diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bbb3821..99bcb304 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: - 'docs' env: - BUILDER_VERSION: v0.9.62 + BUILDER_VERSION: v0.9.75 BUILDER_SOURCE: releases BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net PACKAGE_NAME: aws-iot-device-sdk-js-v2 @@ -32,46 +32,6 @@ env: CI_JOBS_SERVICE_CLIENT_ROLE: arn:aws:iam::180635532705:role/CI_JobsServiceClient_Role jobs: - - al2: - runs-on: ubuntu-latest - permissions: - id-token: write # This is required for requesting the JWT - steps: - - name: configure AWS credentials (containers) - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ env.CI_IOT_CONTAINERS }} - aws-region: ${{ env.AWS_DEFAULT_REGION }} - # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages - - name: Build ${{ env.PACKAGE_NAME }} - run: | - aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh - ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-al2-x64 build -p ${{ env.PACKAGE_NAME }} - - raspberry: - runs-on: ubuntu-latest # latest - permissions: - id-token: write # This is required for requesting the JWT - strategy: - fail-fast: false - matrix: - image: - - raspbian-bullseye - steps: - - name: configure AWS credentials (containers) - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ env.CI_IOT_CONTAINERS }} - aws-region: ${{ env.AWS_DEFAULT_REGION }} - # set arm arch - - name: Install qemu/docker - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - name: Build ${{ env.PACKAGE_NAME }} - run: | - aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh - ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ matrix.image }} build -p ${{ env.PACKAGE_NAME }} - windows: runs-on: windows-latest permissions: @@ -85,7 +45,7 @@ jobs: run: | python -m pip install boto3 - name: configure AWS credentials (PubSub) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -101,7 +61,7 @@ jobs: run: | python ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_windows_cert_connect_cfg.json - name: configure AWS credentials (Device Advisor) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_DEVICE_ADVISOR }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -131,7 +91,7 @@ jobs: source .venv/bin/activate python3 -m pip install boto3 - name: configure AWS credentials (PubSub) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -155,7 +115,7 @@ jobs: source .venv/bin/activate python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pkcs12_connect_cfg.json - name: configure AWS credentials (Device Advisor) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_DEVICE_ADVISOR }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -179,7 +139,7 @@ jobs: run: | python3 -m pip install boto3 - name: configure AWS credentials (PubSub) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -194,7 +154,7 @@ jobs: sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & python ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_electron_cfg.json - name: configure AWS credentials (Device Advisor) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_DEVICE_ADVISOR }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -203,6 +163,42 @@ jobs: cd ./aws-iot-device-sdk-js-v2 python3 ./deviceadvisor/script/DATestRun.py + v2-service-client-tests: + runs-on: ${{ matrix.runner }} + strategy: + matrix: + runner: + - macos-13 + - ubuntu-22.04 + - windows-2022 + permissions: + id-token: write # This is required for requesting the JWT + steps: + - name: Checkout Sources + uses: actions/checkout@v2 + - uses: actions/setup-node@v4 + with: + node-version: 16 + - name: Build ${{ env.PACKAGE_NAME }} + run: | + npm install + - name: Install boto3 + run: | + python3 -m pip install boto3 + - name: configure AWS credentials (MQTT5) + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CI_MQTT5_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Service tests + shell: bash + run: | + source utils/test_setup.sh s3://iot-sdk-ci-bucket-us-east1/IotUsProdMqtt5EnvironmentVariables.txt us-east-1 + npm run test -- iotshadowclientv2 + npm run test -- iotidentityclientv2 + npm run test -- iotjobsclientv2 + source utils/test_cleanup.sh + # Runs the samples and ensures that everything is working linux-smoke-tests: runs-on: ubuntu-22.04 @@ -221,7 +217,7 @@ jobs: sudo apt-get install softhsm2 -y softhsm2-util --version - name: configure AWS credentials (Shadow) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_SHADOW_SERVICE_CLIENT_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -246,7 +242,7 @@ jobs: export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-js-v2/utils python3 ./test_cases/test_shadow_update.py --config-file test_cases/mqtt5_named_shadow_cfg.json - name: configure AWS credentials (Fleet provisioning) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -271,7 +267,7 @@ jobs: export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-js-v2/utils python3 ./test_cases/test_fleet_provisioning.py --config-file test_cases/mqtt5_fleet_provisioning_with_csr_cfg.json --thing-name-prefix Fleet_Thing_ - name: configure AWS credentials (Jobs) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_JOBS_SERVICE_CLIENT_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -286,7 +282,7 @@ jobs: export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-js-v2/utils python3 ./test_cases/test_jobs_execution.py --config-file test_cases/mqtt5_jobs_cfg.json - name: configure AWS credentials (Connect and PubSub) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -316,7 +312,7 @@ jobs: echo "directories.tokendir = /tmp/tokens" > /tmp/softhsm2.conf python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pkcs11_connect_cfg.json - name: configure AWS credentials (Cognito) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_COGNITO_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -324,7 +320,7 @@ jobs: run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_cognito_connect_cfg.json - name: configure AWS credentials (X509) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_X509_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -332,7 +328,7 @@ jobs: run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_x509_connect_cfg.json - name: configure AWS credentials (Custom Authorizer) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_CUSTOM_AUTHORIZER_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -340,40 +336,40 @@ jobs: run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_custom_authorizer_connect_cfg.json - name: configure AWS credentials (Shadow) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_SHADOW_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Shadow sample + - name: run v1 Shadow sample run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_shadow_cfg.json - - name: run Mqtt5 Shadow sample + - name: run v1 Mqtt5 Shadow sample run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_shadow_mqtt5_cfg.json - name: configure AWS credentials (Jobs) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_JOBS_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Jobs sample + - name: run v1 Jobs sample run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_jobs_cfg.json - - name: run Mqtt5 Jobs sample + - name: run v1 Mqtt5 Jobs sample run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_jobs_mqtt5_cfg.json - name: configure AWS credentials (Fleet provisioning) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Fleet Provisioning sample + - name: run v1 Fleet Provisioning sample run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_fleet_provisioning_sample.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_fleet_provisioning_cfg.json --thing-name-prefix "Fleet_Thing_" - - name: run Mqtt5 Fleet Provisioning sample + - name: run v1 Mqtt5 Fleet Provisioning sample run: | python3 ./aws-iot-device-sdk-js-v2/utils/run_fleet_provisioning_sample.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json --thing-name-prefix "Fleet_Thing_" - name: configure AWS credentials (MQTT5 PubSub) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} diff --git a/.github/workflows/ci_run_fleet_provisioning_cfg.json b/.github/workflows/ci_run_fleet_provisioning_cfg.json index 89de917d..d4e2d143 100644 --- a/.github/workflows/ci_run_fleet_provisioning_cfg.json +++ b/.github/workflows/ci_run_fleet_provisioning_cfg.json @@ -1,6 +1,6 @@ { "language": "Javascript", - "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/fleet_provisioning", + "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/deprecated/fleet_provisioning", "runnable_region": "us-east-1", "runnable_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json b/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json index 79965ad5..58a1e101 100644 --- a/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json +++ b/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json @@ -1,6 +1,6 @@ { "language": "Javascript", - "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/fleet_provisioning", + "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/deprecated/fleet_provisioning", "runnable_region": "us-east-1", "runnable_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_jobs_cfg.json b/.github/workflows/ci_run_jobs_cfg.json index bd506248..2da64432 100644 --- a/.github/workflows/ci_run_jobs_cfg.json +++ b/.github/workflows/ci_run_jobs_cfg.json @@ -1,6 +1,6 @@ { "language": "Javascript", - "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/jobs", + "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/deprecated/jobs", "runnable_region": "us-east-1", "runnable_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_jobs_mqtt5_cfg.json b/.github/workflows/ci_run_jobs_mqtt5_cfg.json index f0f10834..f585a72c 100644 --- a/.github/workflows/ci_run_jobs_mqtt5_cfg.json +++ b/.github/workflows/ci_run_jobs_mqtt5_cfg.json @@ -1,6 +1,6 @@ { "language": "Javascript", - "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/jobs", + "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/deprecated/jobs", "runnable_region": "us-east-1", "runnable_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_shadow_cfg.json b/.github/workflows/ci_run_shadow_cfg.json index 51550638..25311f69 100644 --- a/.github/workflows/ci_run_shadow_cfg.json +++ b/.github/workflows/ci_run_shadow_cfg.json @@ -1,6 +1,6 @@ { "language": "Javascript", - "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/shadow", + "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/deprecated/shadow", "runnable_region": "us-east-1", "runnable_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_shadow_mqtt5_cfg.json b/.github/workflows/ci_run_shadow_mqtt5_cfg.json index 78d1515a..e3bd93bc 100644 --- a/.github/workflows/ci_run_shadow_mqtt5_cfg.json +++ b/.github/workflows/ci_run_shadow_mqtt5_cfg.json @@ -1,6 +1,6 @@ { "language": "Javascript", - "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/shadow", + "runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/deprecated/shadow", "runnable_region": "us-east-1", "runnable_main_class": "", "arguments": [ diff --git a/codebuild/samples/shadow-linux.sh b/codebuild/samples/shadow-linux.sh index f17fc2ad..241b7a04 100755 --- a/codebuild/samples/shadow-linux.sh +++ b/codebuild/samples/shadow-linux.sh @@ -5,7 +5,7 @@ set -o pipefail env -pushd $CODEBUILD_SRC_DIR/samples/node/shadow +pushd $CODEBUILD_SRC_DIR/samples/node/deprecated/shadow ENDPOINT=$(aws secretsmanager get-secret-value --secret-id "ci/endpoint" --query "SecretString" | cut -f2 -d":" | sed -e 's/[\\\"\}]//g') diff --git a/docsrc/typedoc-browser.json b/docsrc/typedoc-browser.json index dc8b24e3..2bd8f6f9 100644 --- a/docsrc/typedoc-browser.json +++ b/docsrc/typedoc-browser.json @@ -1,9 +1,11 @@ { "entryPoints": [ "../lib/greengrass/discoveryclient.ts", - "../lib/iotidentity/iotidentityclient.ts", - "../lib/iotjobs/iotjobsclient.ts", - "../lib/iotshadow/iotshadowclient.ts", + "../lib/iotidentity/iotidentity.ts", + "../lib/iotjobs/iotjobs.ts", + "../lib/iotshadow/iotshadow.ts", + "../lib/mqtt_request_response.ts", + "../lib/mqtt_request_response_utils.ts", "../build/docs/aws-crt-nodejs/lib/browser/auth.ts", "../build/docs/aws-crt-nodejs/lib/browser/error.ts", "../build/docs/aws-crt-nodejs/lib/browser/http.ts", diff --git a/docsrc/typedoc-node.json b/docsrc/typedoc-node.json index 0d64dca3..02fb0ded 100644 --- a/docsrc/typedoc-node.json +++ b/docsrc/typedoc-node.json @@ -4,9 +4,11 @@ "../lib/greengrass/discoveryclient.ts", "../lib/greengrasscoreipc.ts", "../lib/greengrasscoreipc/client.ts", - "../lib/iotidentity/iotidentityclient.ts", - "../lib/iotjobs/iotjobsclient.ts", - "../lib/iotshadow/iotshadowclient.ts", + "../lib/iotidentity/iotidentity.ts", + "../lib/iotjobs/iotjobs.ts", + "../lib/iotshadow/iotshadow.ts", + "../lib/mqtt_request_response.ts", + "../lib/mqtt_request_response_utils.ts", "../build/docs/aws-crt-nodejs/lib/native/auth.ts", "../build/docs/aws-crt-nodejs/lib/native/binding.d.ts", "../build/docs/aws-crt-nodejs/lib/native/error.ts", diff --git a/lib/browser.ts b/lib/browser.ts index 0c958165..65ac7fe0 100644 --- a/lib/browser.ts +++ b/lib/browser.ts @@ -10,10 +10,11 @@ * @mergeTarget */ -import * as iotidentity from './iotidentity/iotidentityclient'; import * as greengrass from './greengrass/discoveryclient'; -import * as iotjobs from './iotjobs/iotjobsclient'; -import * as iotshadow from './iotshadow/iotshadowclient'; +import * as iotidentity from './iotidentity/iotidentity'; +import * as iotjobs from './iotjobs/iotjobs'; +import * as iotshadow from './iotshadow/iotshadow'; +import * as mqtt_request_response from './mqtt_request_response'; import { auth, @@ -37,6 +38,7 @@ export { iotshadow, mqtt, mqtt5, + mqtt_request_response, CrtError, ICrtError } diff --git a/lib/eventstream_rpc_utils.ts b/lib/eventstream_rpc_utils.ts index 5a430853..f5017b53 100644 --- a/lib/eventstream_rpc_utils.ts +++ b/lib/eventstream_rpc_utils.ts @@ -20,7 +20,21 @@ import {CrtError, eventstream} from "aws-crt"; * @return a base64-encoded string */ export function encodePayloadAsString(payload : eventstream.Payload) : string { - return Buffer.from(payload).toString("base64"); + if (typeof payload === "string") { + return Buffer.from(payload).toString("base64"); + } + + if (ArrayBuffer.isView(payload)) { + const view = payload as ArrayBufferView; + return Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString("base64"); + } + + if (payload instanceof ArrayBuffer) { + let buffer = payload as ArrayBuffer; + return Buffer.from(buffer).toString("base64"); + } + + throw new TypeError("payload parameter must be a string, ArrayBuffer, or DataView."); } /** diff --git a/lib/index.ts b/lib/index.ts index a94306e3..26884fe5 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -14,12 +14,13 @@ * @packageDocumentation */ -import * as iotidentity from './iotidentity/iotidentityclient'; -import * as greengrass from './greengrass/discoveryclient'; -import * as iotjobs from './iotjobs/iotjobsclient'; -import * as iotshadow from './iotshadow/iotshadowclient'; import * as eventstream_rpc from './eventstream_rpc'; +import * as greengrass from './greengrass/discoveryclient'; import * as greengrasscoreipc from './greengrasscoreipc'; +import * as iotidentity from './iotidentity/iotidentity'; +import * as iotjobs from './iotjobs/iotjobs'; +import * as iotshadow from './iotshadow/iotshadow'; +import * as mqtt_request_response from './mqtt_request_response'; import { auth, @@ -45,6 +46,7 @@ export { iotshadow, mqtt, mqtt5, + mqtt_request_response, CrtError, ICrtError } diff --git a/lib/iotidentity/iotidentity.ts b/lib/iotidentity/iotidentity.ts new file mode 100644 index 00000000..87658b60 --- /dev/null +++ b/lib/iotidentity/iotidentity.ts @@ -0,0 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +/** + * @packageDocumentation + * @module identity + */ + +import * as model from "./model"; +import { IotIdentityClient, IotIdentityError } from "./iotidentityclient"; +import { IotIdentityClientv2 } from "./iotidentityclientv2"; + +export { + IotIdentityClient, + IotIdentityClientv2, + IotIdentityError, + model +}; diff --git a/lib/iotidentity/iotidentityclient.ts b/lib/iotidentity/iotidentityclient.ts index 2b00959f..c3565821 100644 --- a/lib/iotidentity/iotidentityclient.ts +++ b/lib/iotidentity/iotidentityclient.ts @@ -15,8 +15,6 @@ import { mqtt, mqtt5 } from "aws-crt"; import { toUtf8 } from "@aws-sdk/util-utf8-browser" import * as service_client_mqtt_adapter from "../service_client_mqtt_adapter"; -export { model }; - /** * Error subclass for IotIdentity service errors * @@ -79,7 +77,7 @@ export class IotIdentityClient { * * @param client the MQTT5 client to use with this service client * - * @returns a new IotIdentityClient instance + * @return a new IotIdentityClient instance */ static newFromMqtt5Client(client: mqtt5.Mqtt5Client) : IotIdentityClient { let serviceClient: IotIdentityClient = new IotIdentityClient(); @@ -97,7 +95,7 @@ export class IotIdentityClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -124,7 +122,7 @@ export class IotIdentityClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -151,7 +149,7 @@ export class IotIdentityClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -187,7 +185,7 @@ export class IotIdentityClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -234,7 +232,7 @@ export class IotIdentityClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -281,7 +279,7 @@ export class IotIdentityClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -328,7 +326,7 @@ export class IotIdentityClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -375,7 +373,7 @@ export class IotIdentityClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -423,7 +421,7 @@ export class IotIdentityClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * diff --git a/lib/iotidentity/iotidentityclientv2.spec.ts b/lib/iotidentity/iotidentityclientv2.spec.ts new file mode 100644 index 00000000..7ede487f --- /dev/null +++ b/lib/iotidentity/iotidentityclientv2.spec.ts @@ -0,0 +1,317 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + + +import {iot, mqtt as mqtt311, mqtt5, mqtt_request_response} from "aws-crt"; +import {once} from "events"; +import {v4 as uuid} from "uuid"; +import { + CertificateStatus, + DeleteCertificateCommand, + DeleteThingCommand, + UpdateCertificateCommand, + IoTClient +} from "@aws-sdk/client-iot"; + +import {IotIdentityClientv2} from "./iotidentityclientv2"; +import fs from "fs"; + +jest.setTimeout(10000); + +function hasTestEnvironment() : boolean { + if (process.env.AWS_TEST_IOT_CORE_PROVISIONING_HOST === undefined) { + return false; + } + + if (process.env.AWS_TEST_IOT_CORE_PROVISIONING_CERTIFICATE_PATH === undefined) { + return false; + } + + if (process.env.AWS_TEST_IOT_CORE_PROVISIONING_KEY_PATH === undefined) { + return false; + } + + if (process.env.AWS_TEST_IOT_CORE_PROVISIONING_TEMPLATE_NAME === undefined) { + return false; + } + + if (process.env.AWS_TEST_IOT_CORE_PROVISIONING_CSR_PATH === undefined) { + return false; + } + + return true; +} + +const conditional_test = (condition : boolean) => condition ? it : it.skip; + +function build_protocol_client_mqtt5() : mqtt5.Mqtt5Client { + let builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath( + // @ts-ignore + process.env.AWS_TEST_IOT_CORE_PROVISIONING_HOST, + process.env.AWS_TEST_IOT_CORE_PROVISIONING_CERTIFICATE_PATH, + process.env.AWS_TEST_IOT_CORE_PROVISIONING_KEY_PATH + ); + + builder.withConnectProperties({ + clientId : `test-${uuid()}`, + keepAliveIntervalSeconds: 1200, + }); + + return new mqtt5.Mqtt5Client(builder.build()); +} + +function build_protocol_client_mqtt311() : mqtt311.MqttClientConnection { + // @ts-ignore + let builder = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder_from_path(process.env.AWS_TEST_IOT_CORE_PROVISIONING_CERTIFICATE_PATH, process.env.AWS_TEST_IOT_CORE_PROVISIONING_KEY_PATH); + // @ts-ignore + builder.with_endpoint(process.env.AWS_TEST_IOT_CORE_PROVISIONING_HOST); + builder.with_client_id(`test-${uuid()}`); + + let client = new mqtt311.MqttClient(); + return client.new_connection(builder.build()); +} + +enum ProtocolVersion { + Mqtt311, + Mqtt5 +} + +interface TestingOptions { + version: ProtocolVersion, + timeoutSeconds?: number, +} + +class IdentityTestingContext { + + mqtt311Client?: mqtt311.MqttClientConnection; + mqtt5Client?: mqtt5.Mqtt5Client; + + client: IotIdentityClientv2; + + private protocolStarted : boolean = false; + + async startProtocolClient() { + if (!this.protocolStarted) { + this.protocolStarted = true; + if (this.mqtt5Client) { + let connected = once(this.mqtt5Client, mqtt5.Mqtt5Client.CONNECTION_SUCCESS); + this.mqtt5Client.start(); + + await connected; + } + + if (this.mqtt311Client) { + await this.mqtt311Client.connect(); + } + } + } + + async stopProtocolClient() { + if (this.protocolStarted) { + this.protocolStarted = false; + if (this.mqtt5Client) { + let stopped = once(this.mqtt5Client, mqtt5.Mqtt5Client.STOPPED); + this.mqtt5Client.stop(); + await stopped; + + this.mqtt5Client.close(); + } + + if (this.mqtt311Client) { + await this.mqtt311Client.disconnect(); + } + } + } + + constructor(options: TestingOptions) { + if (options.version == ProtocolVersion.Mqtt5) { + this.mqtt5Client = build_protocol_client_mqtt5(); + + let rrOptions : mqtt_request_response.RequestResponseClientOptions = { + maxRequestResponseSubscriptions : 6, + maxStreamingSubscriptions : 2, + operationTimeoutInSeconds : options.timeoutSeconds ?? 60, + } + + this.client = IotIdentityClientv2.newFromMqtt5(this.mqtt5Client, rrOptions); + } else { + this.mqtt311Client = build_protocol_client_mqtt311(); + + let rrOptions : mqtt_request_response.RequestResponseClientOptions = { + maxRequestResponseSubscriptions : 6, + maxStreamingSubscriptions : 2, + operationTimeoutInSeconds : options.timeoutSeconds ?? 60, + } + + this.client = IotIdentityClientv2.newFromMqtt311(this.mqtt311Client, rrOptions); + } + } + + async open() { + await this.startProtocolClient(); + } + + async close() { + this.client.close(); + await this.stopProtocolClient(); + } +} + +async function doCreateDestroyTest(version: ProtocolVersion) { + let context = new IdentityTestingContext({ + version: version + }); + await context.open(); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('identityv2 - create destroy mqtt5', async () => { + await doCreateDestroyTest(ProtocolVersion.Mqtt5); +}); + +conditional_test(hasTestEnvironment())('identityv2 - create destroy mqtt311', async () => { + await doCreateDestroyTest(ProtocolVersion.Mqtt311); +}); + +interface TestResources { + certificateId?: string, + thingName?: string +} + +//@ts-ignore +let identityResources : TestResources = {}; + +beforeEach(async () => { + identityResources = {} +}); + +afterEach(async () => { + const client = new IoTClient({}); + + if (identityResources.thingName) { + await new Promise(r => setTimeout(r, 1000)); + + const command = new DeleteThingCommand({ + thingName: identityResources.thingName + }); + + try { + await client.send(command); + } catch (e) {} + } + + if (identityResources.certificateId) { + await new Promise(r => setTimeout(r, 1000)); + + const deactivateCommand = new UpdateCertificateCommand({ + certificateId: identityResources.certificateId, + newStatus: CertificateStatus.INACTIVE + }); + + try { + await client.send(deactivateCommand); + } catch (e) {} + + await new Promise(r => setTimeout(r, 1000)); + + const deleteCommand = new DeleteCertificateCommand({ + certificateId: identityResources.certificateId, + }); + + try { + await client.send(deleteCommand); + } catch (e) {} + } + + identityResources = {} +}); + +async function doProvisioningTest(version: ProtocolVersion) { + + let context = new IdentityTestingContext({ + version: version + }); + await context.open(); + + let createKeysResponse = await context.client.createKeysAndCertificate({}); + identityResources.certificateId = createKeysResponse.certificateId; + expect(createKeysResponse.certificateId).toBeDefined(); + expect(createKeysResponse.certificatePem).toBeDefined(); + expect(createKeysResponse.privateKey).toBeDefined(); + expect(createKeysResponse.certificateOwnershipToken).toBeDefined(); + + const params: { [key: string]: string } = JSON.parse(`{"SerialNumber":"${uuid()}"}`); + + let registerThingResponse = await context.client.registerThing({ + // @ts-ignore + templateName: process.env.AWS_TEST_IOT_CORE_PROVISIONING_TEMPLATE_NAME, + certificateOwnershipToken: createKeysResponse.certificateOwnershipToken, + parameters: params + }); + identityResources.thingName = registerThingResponse.thingName; + expect(registerThingResponse.thingName).toBeDefined(); + + context.client.close(); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('identityv2 provisioning mqtt5', async () => { + await doProvisioningTest(ProtocolVersion.Mqtt5); +}); + + +conditional_test(hasTestEnvironment())('identityv2 provisioning mqtt311', async () => { + await doProvisioningTest(ProtocolVersion.Mqtt311); +}); + +async function doCsrProvisioningTest(version: ProtocolVersion) { + let csr: string = ""; + try { + csr = fs.readFileSync(process.env.AWS_TEST_IOT_CORE_PROVISIONING_CSR_PATH as string, 'utf8'); + } catch (e) { + if (e instanceof Error) { + console.log('Error reading CSR PEM file:', e.stack); + } + } + + let context = new IdentityTestingContext({ + version: version + }); + await context.open(); + + let createCertificateFromCsrResponse = await context.client.createCertificateFromCsr({ + certificateSigningRequest: csr + }); + identityResources.certificateId = createCertificateFromCsrResponse.certificateId; + expect(createCertificateFromCsrResponse.certificateId).toBeDefined(); + expect(createCertificateFromCsrResponse.certificatePem).toBeDefined(); + expect(createCertificateFromCsrResponse.certificateOwnershipToken).toBeDefined(); + + const params: { [key: string]: string } = JSON.parse(`{"SerialNumber":"${uuid()}"}`); + + let registerThingResponse = await context.client.registerThing({ + // @ts-ignore + templateName: process.env.AWS_TEST_IOT_CORE_PROVISIONING_TEMPLATE_NAME, + certificateOwnershipToken: createCertificateFromCsrResponse.certificateOwnershipToken, + parameters: params + }); + identityResources.thingName = registerThingResponse.thingName; + expect(registerThingResponse.thingName).toBeDefined(); + + context.client.close(); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('identityv2 CSR provisioning mqtt5', async () => { + await doCsrProvisioningTest(ProtocolVersion.Mqtt5); +}); + + +conditional_test(hasTestEnvironment())('identityv2 CSR provisioning mqtt311', async () => { + await doCsrProvisioningTest(ProtocolVersion.Mqtt311); +}); \ No newline at end of file diff --git a/lib/iotidentity/iotidentityclientv2.ts b/lib/iotidentity/iotidentityclientv2.ts new file mode 100644 index 00000000..39e55085 --- /dev/null +++ b/lib/iotidentity/iotidentityclientv2.ts @@ -0,0 +1,145 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +/** + * @packageDocumentation + * @module identity + */ + +import {mqtt, mqtt5} from 'aws-crt'; +import {mqtt_request_response as mqtt_rr_internal} from 'aws-crt'; +import * as mqtt_request_response_utils from '../mqtt_request_response_utils'; +import * as model from './model'; +import * as v2utils from './v2utils'; + +/** + * An AWS IoT service that assists with provisioning a device and installing unique client certificates on it + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html + * + * @category IotIdentity + */ +export class IotIdentityClientv2 { + private rrClient : mqtt_rr_internal.RequestResponseClient; + private serviceModel : mqtt_request_response_utils.RequestResponseServiceModel; + + private constructor(rrClient: mqtt_rr_internal.RequestResponseClient) { + this.rrClient = rrClient; + this.serviceModel = v2utils.makeServiceModel(); + } + + /** + * Creates a new service client that will use an SDK MQTT 311 client as transport. + * + * @param protocolClient the MQTT 311 client to use for transport + * @param options additional service client configuration options + * + * @return a new service client + * + */ + static newFromMqtt311(protocolClient: mqtt.MqttClientConnection, options: mqtt_rr_internal.RequestResponseClientOptions) : IotIdentityClientv2 { + let rrClient = mqtt_rr_internal.RequestResponseClient.newFromMqtt311(protocolClient, options); + return new IotIdentityClientv2(rrClient); + } + + /** + * Creates a new service client that will use an SDK MQTT 5 client as transport. + * + * @param protocolClient the MQTT 5 client to use for transport + * @param options additional service client configuration options + * + * @return a new service client + * + */ + static newFromMqtt5(protocolClient: mqtt5.Mqtt5Client, options: mqtt_rr_internal.RequestResponseClientOptions) : IotIdentityClientv2 { + let rrClient = mqtt_rr_internal.RequestResponseClient.newFromMqtt5(protocolClient, options); + return new IotIdentityClientv2(rrClient); + } + + /** + * Triggers cleanup of all resources associated with the service client. Closing a client will fail + * all incomplete requests and close all unclosed streaming operations. + * + * This must be called when finished with a client; otherwise, native resources will leak. + */ + close() { + this.rrClient.close(); + } + + /** + * Creates a certificate from a certificate signing request (CSR). AWS IoT provides client certificates that are signed by the Amazon Root certificate authority (CA). The new certificate has a PENDING_ACTIVATION status. When you call RegisterThing to provision a thing with this certificate, the certificate status changes to ACTIVE or INACTIVE as described in the template. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#fleet-provision-api + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotIdentity + */ + async createCertificateFromCsr(request: model.CreateCertificateFromCsrRequest) : Promise { + + let config = { + operationName: "createCertificateFromCsr", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Creates new keys and a certificate. AWS IoT provides client certificates that are signed by the Amazon Root certificate authority (CA). The new certificate has a PENDING_ACTIVATION status. When you call RegisterThing to provision a thing with this certificate, the certificate status changes to ACTIVE or INACTIVE as described in the template. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#fleet-provision-api + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotIdentity + */ + async createKeysAndCertificate(request: model.CreateKeysAndCertificateRequest) : Promise { + + let config = { + operationName: "createKeysAndCertificate", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Provisions an AWS IoT thing using a pre-defined template. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#fleet-provision-api + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotIdentity + */ + async registerThing(request: model.RegisterThingRequest) : Promise { + + let config = { + operationName: "registerThing", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + +} diff --git a/lib/iotidentity/model.ts b/lib/iotidentity/model.ts index 70f2a40f..b12f4926 100644 --- a/lib/iotidentity/model.ts +++ b/lib/iotidentity/model.ts @@ -198,3 +198,30 @@ export interface RegisterThingSubscriptionRequest { } +/** + * Response document containing details about a failed request. + * + * @category IotIdentity + */ +export interface V2ErrorResponse { + + /** + * Response status code + * + */ + statusCode?: number; + + /** + * Response error code + * + */ + errorCode?: string; + + /** + * Response error message + * + */ + errorMessage?: string; + +} + diff --git a/lib/iotidentity/v2utils.ts b/lib/iotidentity/v2utils.ts new file mode 100644 index 00000000..360d9d4b --- /dev/null +++ b/lib/iotidentity/v2utils.ts @@ -0,0 +1,259 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +import * as model from "./model"; +import {fromUtf8, toUtf8} from "@aws-sdk/util-utf8-browser"; +import * as model_validation_utils from "../mqtt_request_response_utils"; +import * as mqtt_request_response_utils from "../mqtt_request_response_utils"; + +function normalizeCreateCertificateFromCsrRequest(value: model.CreateCertificateFromCsrRequest) : any { + let normalizedValue : any = {}; + + if (value.certificateSigningRequest) { + normalizedValue.certificateSigningRequest = value.certificateSigningRequest; + } + + return normalizedValue; +} + +function buildCreateCertificateFromCsrRequestPayload(request: any) : ArrayBuffer { + let value = normalizeCreateCertificateFromCsrRequest(request as model.CreateCertificateFromCsrRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToCreateCertificateFromCsrRequest(request: any) : [any, string | undefined] { + return [request, undefined]; +} + +function normalizeCreateKeysAndCertificateRequest(value: model.CreateKeysAndCertificateRequest) : any { + let normalizedValue : any = {}; + + + return normalizedValue; +} + +function buildCreateKeysAndCertificateRequestPayload(request: any) : ArrayBuffer { + let value = normalizeCreateKeysAndCertificateRequest(request as model.CreateKeysAndCertificateRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToCreateKeysAndCertificateRequest(request: any) : [any, string | undefined] { + return [request, undefined]; +} + +function normalizeRegisterThingRequest(value: model.RegisterThingRequest) : any { + let normalizedValue : any = {}; + + if (value.certificateOwnershipToken) { + normalizedValue.certificateOwnershipToken = value.certificateOwnershipToken; + } + if (value.parameters) { + normalizedValue.parameters = value.parameters; + } + + return normalizedValue; +} + +function buildRegisterThingRequestPayload(request: any) : ArrayBuffer { + let value = normalizeRegisterThingRequest(request as model.RegisterThingRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToRegisterThingRequest(request: any) : [any, string | undefined] { + return [request, undefined]; +} + +function buildCreateCertificateFromCsrSubscriptions(request: any) : Array { + + return new Array( + `$aws/certificates/create-from-csr/json/accepted`, + `$aws/certificates/create-from-csr/json/rejected`, + ); +} + +function buildCreateCertificateFromCsrPublishTopic(request: any) : string { + + return `$aws/certificates/create-from-csr/json`; +} + +function buildCreateCertificateFromCsrResponsePaths(request: any) : Array { + + return new Array( + { + topic: `$aws/certificates/create-from-csr/json/accepted`, + deserializer: deserializeCreateCertificateFromCsrResponse, + }, + { + topic: `$aws/certificates/create-from-csr/json/rejected`, + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildCreateKeysAndCertificateSubscriptions(request: any) : Array { + + return new Array( + `$aws/certificates/create/json/accepted`, + `$aws/certificates/create/json/rejected`, + ); +} + +function buildCreateKeysAndCertificatePublishTopic(request: any) : string { + + return `$aws/certificates/create/json`; +} + +function buildCreateKeysAndCertificateResponsePaths(request: any) : Array { + + return new Array( + { + topic: `$aws/certificates/create/json/accepted`, + deserializer: deserializeCreateKeysAndCertificateResponse, + }, + { + topic: `$aws/certificates/create/json/rejected`, + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildRegisterThingSubscriptions(request: any) : Array { + let typedRequest: model.RegisterThingRequest = request; + + return new Array( + `$aws/provisioning-templates/${typedRequest.templateName}/provision/json/accepted`, + `$aws/provisioning-templates/${typedRequest.templateName}/provision/json/rejected`, + ); +} + +function buildRegisterThingPublishTopic(request: any) : string { + let typedRequest: model.RegisterThingRequest = request; + + return `$aws/provisioning-templates/${typedRequest.templateName}/provision/json`; +} + +function buildRegisterThingResponsePaths(request: any) : Array { + let typedRequest: model.RegisterThingRequest = request; + + return new Array( + { + topic: `$aws/provisioning-templates/${typedRequest.templateName}/provision/json/accepted`, + deserializer: deserializeRegisterThingResponse, + }, + { + topic: `$aws/provisioning-templates/${typedRequest.templateName}/provision/json/rejected`, + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function deserializeCreateCertificateFromCsrResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeCreateKeysAndCertificateResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeRegisterThingResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeV2ErrorResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function createRequestResponseOperationServiceModelMap() : Map { + return new Map([ + ["createCertificateFromCsr", { + inputShapeName: "CreateCertificateFromCsrRequest", + payloadTransformer: buildCreateCertificateFromCsrRequestPayload, + subscriptionGenerator: buildCreateCertificateFromCsrSubscriptions, + responsePathGenerator: buildCreateCertificateFromCsrResponsePaths, + publishTopicGenerator: buildCreateCertificateFromCsrPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToCreateCertificateFromCsrRequest, + }], + ["createKeysAndCertificate", { + inputShapeName: "CreateKeysAndCertificateRequest", + payloadTransformer: buildCreateKeysAndCertificateRequestPayload, + subscriptionGenerator: buildCreateKeysAndCertificateSubscriptions, + responsePathGenerator: buildCreateKeysAndCertificateResponsePaths, + publishTopicGenerator: buildCreateKeysAndCertificatePublishTopic, + correlationTokenApplicator: applyCorrelationTokenToCreateKeysAndCertificateRequest, + }], + ["registerThing", { + inputShapeName: "RegisterThingRequest", + payloadTransformer: buildRegisterThingRequestPayload, + subscriptionGenerator: buildRegisterThingSubscriptions, + responsePathGenerator: buildRegisterThingResponsePaths, + publishTopicGenerator: buildRegisterThingPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToRegisterThingRequest, + }], + ]); +} + + + +function createStreamingOperationServiceModelMap() : Map { + return new Map([ + ]); +} + +function validateCreateCertificateFromCsrRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.CreateCertificateFromCsrRequest = value; + + model_validation_utils.validateValueAsString(value.certificateSigningRequest, 'certificateSigningRequest'); +} + +function validateCreateKeysAndCertificateRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.CreateKeysAndCertificateRequest = value; + +} + +function validateRegisterThingRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.RegisterThingRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.templateName, 'templateName'); + model_validation_utils.validateValueAsString(value.certificateOwnershipToken, 'certificateOwnershipToken'); + model_validation_utils.validateValueAsOptionalMap(value.parameters, model_validation_utils.validateValueAsString, model_validation_utils.validateValueAsString, 'parameters'); +} + + +function createValidatorMap() : Map void> { + return new Map void>([ + ["CreateCertificateFromCsrRequest", validateCreateCertificateFromCsrRequest], + ["CreateKeysAndCertificateRequest", validateCreateKeysAndCertificateRequest], + ["RegisterThingRequest", validateRegisterThingRequest], + ]); +} + +export function makeServiceModel() : mqtt_request_response_utils.RequestResponseServiceModel { + let model : mqtt_request_response_utils.RequestResponseServiceModel = { + requestResponseOperations: createRequestResponseOperationServiceModelMap(), + streamingOperations: createStreamingOperationServiceModelMap(), + shapeValidators: createValidatorMap() + }; + + return model; +} diff --git a/lib/iotjobs/iotjobs.ts b/lib/iotjobs/iotjobs.ts new file mode 100644 index 00000000..6b6494ac --- /dev/null +++ b/lib/iotjobs/iotjobs.ts @@ -0,0 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +/** + * @packageDocumentation + * @module jobs + */ + +import * as model from "./model"; +import { IotJobsClient, IotJobsError } from "./iotjobsclient"; +import { IotJobsClientv2 } from "./iotjobsclientv2"; + +export { + IotJobsClient, + IotJobsClientv2, + IotJobsError, + model +}; diff --git a/lib/iotjobs/iotjobsclient.ts b/lib/iotjobs/iotjobsclient.ts index b59a8514..db4a1560 100644 --- a/lib/iotjobs/iotjobsclient.ts +++ b/lib/iotjobs/iotjobsclient.ts @@ -15,8 +15,6 @@ import { mqtt, mqtt5 } from "aws-crt"; import { toUtf8 } from "@aws-sdk/util-utf8-browser" import * as service_client_mqtt_adapter from "../service_client_mqtt_adapter"; -export { model }; - /** * Error subclass for IotJobs service errors * @@ -79,7 +77,7 @@ export class IotJobsClient { * * @param client the MQTT5 client to use with this service client * - * @returns a new IotJobsClient instance + * @return a new IotJobsClient instance */ static newFromMqtt5Client(client: mqtt5.Mqtt5Client) : IotJobsClient { let serviceClient: IotJobsClient = new IotJobsClient(); @@ -97,7 +95,7 @@ export class IotJobsClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -126,7 +124,7 @@ export class IotJobsClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -154,7 +152,7 @@ export class IotJobsClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -182,7 +180,7 @@ export class IotJobsClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -219,7 +217,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -268,7 +266,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -317,7 +315,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -365,7 +363,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -413,7 +411,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -461,7 +459,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -509,7 +507,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -557,7 +555,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -605,7 +603,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -654,7 +652,7 @@ export class IotJobsClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * diff --git a/lib/iotjobs/iotjobsclientv2.spec.ts b/lib/iotjobs/iotjobsclientv2.spec.ts new file mode 100644 index 00000000..d82d8e09 --- /dev/null +++ b/lib/iotjobs/iotjobsclientv2.spec.ts @@ -0,0 +1,391 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + + +import {iot, mqtt as mqtt311, mqtt5, mqtt_request_response} from "aws-crt"; +import {v4 as uuid} from "uuid"; +import {once} from "events"; +import {IotJobsClientv2} from "./iotjobsclientv2"; +import { + AddThingToThingGroupCommand, + CreateJobCommand, + CreateThingCommand, + CreateThingGroupCommand, + DeleteJobCommand, + DeleteThingCommand, + DeleteThingGroupCommand, + IoTClient +} from "@aws-sdk/client-iot"; +import * as model from "./model"; + +jest.setTimeout(30000); + +function hasTestEnvironment() : boolean { + if (process.env.AWS_TEST_MQTT5_IOT_CORE_HOST === undefined) { + return false; + } + + if (process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH === undefined) { + return false; + } + + if (process.env.AWS_TEST_MQTT5_IOT_KEY_PATH === undefined) { + return false; + } + + if (process.env.AWS_TEST_MQTT5_IOT_CORE_REGION === undefined) { + return false; + } + + return true; +} + +const conditional_test = (condition : boolean) => condition ? it : it.skip; + +function build_protocol_client_mqtt5() : mqtt5.Mqtt5Client { + let builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath( + // @ts-ignore + process.env.AWS_TEST_MQTT5_IOT_CORE_HOST, + process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH, + process.env.AWS_TEST_MQTT5_IOT_KEY_PATH + ); + + builder.withConnectProperties({ + clientId : `test-${uuid()}`, + keepAliveIntervalSeconds: 1200, + }); + + return new mqtt5.Mqtt5Client(builder.build()); +} + +function build_protocol_client_mqtt311() : mqtt311.MqttClientConnection { + // @ts-ignore + let builder = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder_from_path(process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH, process.env.AWS_TEST_MQTT5_IOT_KEY_PATH); + // @ts-ignore + builder.with_endpoint(process.env.AWS_TEST_MQTT5_IOT_CORE_HOST); + builder.with_client_id(`test-${uuid()}`); + + let client = new mqtt311.MqttClient(); + return client.new_connection(builder.build()); +} + +enum ProtocolVersion { + Mqtt311, + Mqtt5 +} + +interface TestingOptions { + version: ProtocolVersion, + timeoutSeconds?: number, +} + +class JobsTestingContext { + + mqtt311Client?: mqtt311.MqttClientConnection; + mqtt5Client?: mqtt5.Mqtt5Client; + + client: IotJobsClientv2; + + private protocolStarted : boolean = false; + + async startProtocolClient() { + if (!this.protocolStarted) { + this.protocolStarted = true; + if (this.mqtt5Client) { + let connected = once(this.mqtt5Client, mqtt5.Mqtt5Client.CONNECTION_SUCCESS); + this.mqtt5Client.start(); + + await connected; + } + + if (this.mqtt311Client) { + await this.mqtt311Client.connect(); + } + } + } + + async stopProtocolClient() { + if (this.protocolStarted) { + this.protocolStarted = false; + if (this.mqtt5Client) { + let stopped = once(this.mqtt5Client, mqtt5.Mqtt5Client.STOPPED); + this.mqtt5Client.stop(); + await stopped; + + this.mqtt5Client.close(); + } + + if (this.mqtt311Client) { + await this.mqtt311Client.disconnect(); + } + } + } + + constructor(options: TestingOptions) { + if (options.version == ProtocolVersion.Mqtt5) { + this.mqtt5Client = build_protocol_client_mqtt5(); + + let rrOptions : mqtt_request_response.RequestResponseClientOptions = { + maxRequestResponseSubscriptions : 6, + maxStreamingSubscriptions : 2, + operationTimeoutInSeconds : options.timeoutSeconds ?? 60, + } + + this.client = IotJobsClientv2.newFromMqtt5(this.mqtt5Client, rrOptions); + } else { + this.mqtt311Client = build_protocol_client_mqtt311(); + + let rrOptions : mqtt_request_response.RequestResponseClientOptions = { + maxRequestResponseSubscriptions : 6, + maxStreamingSubscriptions : 2, + operationTimeoutInSeconds : options.timeoutSeconds ?? 60, + } + + this.client = IotJobsClientv2.newFromMqtt311(this.mqtt311Client, rrOptions); + } + } + + async open() { + await this.startProtocolClient(); + } + + async close() { + this.client.close(); + await this.stopProtocolClient(); + } +} + +async function doCreateDestroyTest(version: ProtocolVersion) { + let context = new JobsTestingContext({ + version: version + }); + await context.open(); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('jobsv2 - create destroy mqtt5', async () => { + await doCreateDestroyTest(ProtocolVersion.Mqtt5); +}); + +conditional_test(hasTestEnvironment())('jobsv2 - create destroy mqtt311', async () => { + await doCreateDestroyTest(ProtocolVersion.Mqtt311); +}); + +interface TestResources { + thingGroupName?: string, + thingGroupArn?: string, + thingName?: string, + + jobId1?: string, +} + +//@ts-ignore +let jobResources : TestResources = {}; + +async function createJob(client : IoTClient, index: number) : Promise { + let jobId = 'jobid-' + uuid(); + let jobDocument = { + test: `do-something${index}` + }; + + const createJobCommand = new CreateJobCommand({ + jobId: jobId, + targets: [ jobResources.thingGroupArn ?? "" ], + document: JSON.stringify(jobDocument), + targetSelection: "CONTINUOUS" + }); + + await client.send(createJobCommand); + + return jobId; +} + +async function deleteJob(client: IoTClient, jobId: string | undefined) : Promise { + if (jobId) { + const command = new DeleteJobCommand({ + jobId: jobId, + force: true + }); + + await client.send(command); + } +} + +beforeEach(async () => { + jobResources = {} + const client = new IoTClient({ + region: process.env.AWS_TEST_MQTT5_IOT_CORE_REGION, + }); + + let thingGroupName = 'tgn-' + uuid(); + + const createThingGroupCommand = new CreateThingGroupCommand({ + thingGroupName: thingGroupName + }); + + const createThingGroupResponse = await client.send(createThingGroupCommand); + jobResources.thingGroupName = thingGroupName; + jobResources.thingGroupArn = createThingGroupResponse.thingGroupArn; + + let thingName = 't-' + uuid(); + const createThingCommand = new CreateThingCommand({ + thingName: thingName + }); + + await client.send(createThingCommand); + jobResources.thingName = thingName; + + await new Promise(r => setTimeout(r, 1000)); + + jobResources.jobId1 = await createJob(client, 1); + + await new Promise(r => setTimeout(r, 1000)); +}); + +afterEach(async () => { + const client = new IoTClient({ + region: process.env.AWS_TEST_MQTT5_IOT_CORE_REGION, + }); + + await new Promise(r => setTimeout(r, 1000)); + + await deleteJob(client, jobResources.jobId1); + + await new Promise(r => setTimeout(r, 1000)); + + if (jobResources.thingName) { + const command = new DeleteThingCommand({ + thingName: jobResources.thingName + }); + + await client.send(command); + + await new Promise(r => setTimeout(r, 1000)); + } + + if (jobResources.thingGroupName) { + const command = new DeleteThingGroupCommand({ + thingGroupName: jobResources.thingGroupName + }); + + await client.send(command); + } + + jobResources = {} +}); + +async function verifyNoJobExecutions(context: JobsTestingContext) { + let response = await context.client.getPendingJobExecutions({ + thingName: jobResources.thingName ?? "" + }); + // @ts-ignore + expect(response.inProgressJobs.length).toEqual(0); + // @ts-ignore + expect(response.queuedJobs.length).toEqual(0); +} + +async function attachThingToThingGroup(client: IoTClient) { + + const addThingToThingGroupCommand = new AddThingToThingGroupCommand({ + thingName: jobResources.thingName, + thingGroupName: jobResources.thingGroupName + }); + + await client.send(addThingToThingGroupCommand); +} + +async function doProcessingTest(version: ProtocolVersion) { + const client = new IoTClient({}); + + let context = new JobsTestingContext({ + version: version + }); + await context.open(); + + // set up streaming operations for our test's thing + let jobExecutionChangedStream = context.client.createJobExecutionsChangedStream({ + thingName: jobResources.thingName ?? "" + }); + jobExecutionChangedStream.open(); + + let nextJobExecutionChangedStream = context.client.createNextJobExecutionChangedStream({ + thingName: jobResources.thingName ?? "" + }); + nextJobExecutionChangedStream.open(); + + let initialExecutionChangedWaiter = once(jobExecutionChangedStream, 'incomingPublish'); + let initialNextJobExecutionChangedWaiter = once(nextJobExecutionChangedStream, 'incomingPublish'); + + // thing is brand new, nothing should be pending + await verifyNoJobExecutions(context); + + // as soon as we attach the thing to the thing group which has a continuous job associated with it, a + // job execution should become queued for our thing + await attachThingToThingGroup(client); + + let initialJobExecutionChanged : model.JobExecutionsChangedEvent = (await initialExecutionChangedWaiter)[0].message; + // @ts-ignore + expect(initialJobExecutionChanged.jobs['QUEUED'].length).toEqual(1); + // @ts-ignore + expect(initialJobExecutionChanged.jobs['QUEUED'][0].jobId).toEqual(jobResources.jobId1); + + let initialNextJobExecutionChanged : model.NextJobExecutionChangedEvent = (await initialNextJobExecutionChangedWaiter)[0].message; + expect(initialNextJobExecutionChanged.execution?.jobId).toEqual(jobResources.jobId1); + expect(initialNextJobExecutionChanged.execution?.status).toEqual(model.JobStatus.QUEUED); + + let finalExecutionChangedWaiter = once(jobExecutionChangedStream, 'incomingPublish'); + let finalNextJobExecutionChangedWaiter = once(nextJobExecutionChangedStream, 'incomingPublish'); + + // tell the service we'll run the next job + let startNextResponse = await context.client.startNextPendingJobExecution({ + thingName: jobResources.thingName ?? "" + }); + expect(startNextResponse.execution?.jobId).toEqual(jobResources.jobId1); + + // pretend to do the job + await new Promise(r => setTimeout(r, 1000)); + + // job execution should be in progress + let describeResponse = await context.client.describeJobExecution({ + thingName: jobResources.thingName ?? "", + jobId: jobResources.jobId1 ?? "", + }); + expect(describeResponse.execution?.jobId).toEqual(jobResources.jobId1); + expect(describeResponse.execution?.status).toEqual(model.JobStatus.IN_PROGRESS); + + // tell the service we completed the job successfully + await context.client.updateJobExecution({ + thingName: jobResources.thingName ?? "", + jobId: jobResources.jobId1 ?? "", + status: model.JobStatus.SUCCEEDED + }); + + await new Promise(r => setTimeout(r, 3000)); + + let finalJobExecutionChanged : model.JobExecutionsChangedEvent = (await finalExecutionChangedWaiter)[0].message; + expect(finalJobExecutionChanged.jobs).toEqual({}); + + let finalNextJobExecutionChanged : model.NextJobExecutionChangedEvent = (await finalNextJobExecutionChangedWaiter)[0].message; + expect(finalNextJobExecutionChanged.timestamp).toBeDefined(); + + let getPendingResponse = await context.client.getPendingJobExecutions({ + thingName: jobResources.thingName ?? "" + }); + expect(getPendingResponse.queuedJobs?.length).toEqual(0); + expect(getPendingResponse.inProgressJobs?.length).toEqual(0); + + context.client.close(); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('jobsv2 processing mqtt5', async () => { + await doProcessingTest(ProtocolVersion.Mqtt5); +}); + +conditional_test(hasTestEnvironment())('jobsv2 processing mqtt311', async () => { + await doProcessingTest(ProtocolVersion.Mqtt311); +}); \ No newline at end of file diff --git a/lib/iotjobs/iotjobsclientv2.ts b/lib/iotjobs/iotjobsclientv2.ts new file mode 100644 index 00000000..4374d5a7 --- /dev/null +++ b/lib/iotjobs/iotjobsclientv2.ts @@ -0,0 +1,222 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +/** + * @packageDocumentation + * @module jobs + */ + +import {mqtt, mqtt5} from 'aws-crt'; +import {mqtt_request_response as mqtt_rr_internal} from 'aws-crt'; +import * as mqtt_request_response from '../mqtt_request_response'; +import * as mqtt_request_response_utils from '../mqtt_request_response_utils'; +import * as model from './model'; +import * as v2utils from './v2utils'; + +/** + * The AWS IoT jobs service can be used to define a set of remote operations that are sent to and executed on one or more devices connected to AWS IoT. + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#jobs-mqtt-api + * + * @category IotJobs + */ +export class IotJobsClientv2 { + private rrClient : mqtt_rr_internal.RequestResponseClient; + private serviceModel : mqtt_request_response_utils.RequestResponseServiceModel; + + private constructor(rrClient: mqtt_rr_internal.RequestResponseClient) { + this.rrClient = rrClient; + this.serviceModel = v2utils.makeServiceModel(); + } + + /** + * Creates a new service client that will use an SDK MQTT 311 client as transport. + * + * @param protocolClient the MQTT 311 client to use for transport + * @param options additional service client configuration options + * + * @return a new service client + * + */ + static newFromMqtt311(protocolClient: mqtt.MqttClientConnection, options: mqtt_rr_internal.RequestResponseClientOptions) : IotJobsClientv2 { + let rrClient = mqtt_rr_internal.RequestResponseClient.newFromMqtt311(protocolClient, options); + return new IotJobsClientv2(rrClient); + } + + /** + * Creates a new service client that will use an SDK MQTT 5 client as transport. + * + * @param protocolClient the MQTT 5 client to use for transport + * @param options additional service client configuration options + * + * @return a new service client + * + */ + static newFromMqtt5(protocolClient: mqtt5.Mqtt5Client, options: mqtt_rr_internal.RequestResponseClientOptions) : IotJobsClientv2 { + let rrClient = mqtt_rr_internal.RequestResponseClient.newFromMqtt5(protocolClient, options); + return new IotJobsClientv2(rrClient); + } + + /** + * Triggers cleanup of all resources associated with the service client. Closing a client will fail + * all incomplete requests and close all unclosed streaming operations. + * + * This must be called when finished with a client; otherwise, native resources will leak. + */ + close() { + this.rrClient.close(); + } + + /** + * Gets detailed information about a job execution. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotJobs + */ + async describeJobExecution(request: model.DescribeJobExecutionRequest) : Promise { + + let config = { + operationName: "describeJobExecution", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Gets the list of all jobs for a thing that are not in a terminal state. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-getpendingjobexecutions + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotJobs + */ + async getPendingJobExecutions(request: model.GetPendingJobExecutionsRequest) : Promise { + + let config = { + operationName: "getPendingJobExecutions", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Gets and starts the next pending job execution for a thing (status IN_PROGRESS or QUEUED). + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-startnextpendingjobexecution + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotJobs + */ + async startNextPendingJobExecution(request: model.StartNextPendingJobExecutionRequest) : Promise { + + let config = { + operationName: "startNextPendingJobExecution", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Updates the status of a job execution. You can optionally create a step timer by setting a value for the stepTimeoutInMinutes property. If you don't update the value of this property by running UpdateJobExecution again, the job execution times out when the step timer expires. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-updatejobexecution + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotJobs + */ + async updateJobExecution(request: model.UpdateJobExecutionRequest) : Promise { + + let config = { + operationName: "updateJobExecution", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Creates a stream of JobExecutionsChanged notifications for a given IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-jobexecutionschanged + * + * @param config streaming operation configuration options + * + * @return a streaming operation which will emit an event every time a message is received on the + * associated MQTT topic + * + * @category IotJobs + */ + createJobExecutionsChangedStream(config: model.JobExecutionsChangedSubscriptionRequest) + : mqtt_request_response.StreamingOperation { + + let streamingOperationConfig : mqtt_request_response_utils.StreamingOperationConfig = { + operationName: "createJobExecutionsChangedStream", + serviceModel: this.serviceModel, + client: this.rrClient, + modelConfig: config, + }; + + return mqtt_request_response.StreamingOperation.create(streamingOperationConfig); + } + + /** + * + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-nextjobexecutionchanged + * + * @param config streaming operation configuration options + * + * @return a streaming operation which will emit an event every time a message is received on the + * associated MQTT topic + * + * @category IotJobs + */ + createNextJobExecutionChangedStream(config: model.NextJobExecutionChangedSubscriptionRequest) + : mqtt_request_response.StreamingOperation { + + let streamingOperationConfig : mqtt_request_response_utils.StreamingOperationConfig = { + operationName: "createNextJobExecutionChangedStream", + serviceModel: this.serviceModel, + client: this.rrClient, + modelConfig: config, + }; + + return mqtt_request_response.StreamingOperation.create(streamingOperationConfig); + } + +} diff --git a/lib/iotjobs/model.ts b/lib/iotjobs/model.ts index 76a012e2..b3e4f710 100644 --- a/lib/iotjobs/model.ts +++ b/lib/iotjobs/model.ts @@ -5,6 +5,32 @@ * This file is generated */ +/** + * The status of the job execution. + * + * @category IotJobs + */ +export enum JobStatus { + + UNKNOWN_ENUM_VALUE = "UNKNOWN_ENUM_VALUE", + + IN_PROGRESS = "IN_PROGRESS", + + FAILED = "FAILED", + + QUEUED = "QUEUED", + + TIMED_OUT = "TIMED_OUT", + + SUCCEEDED = "SUCCEEDED", + + CANCELED = "CANCELED", + + REJECTED = "REJECTED", + + REMOVED = "REMOVED", +} + /** * A value indicating the kind of error encountered while processing an AWS IoT Jobs request * @@ -60,32 +86,6 @@ export enum RejectedErrorCode { VERSION_MISMATCH = "VersionMismatch", } -/** - * The status of the job execution. - * - * @category IotJobs - */ -export enum JobStatus { - - UNKNOWN_ENUM_VALUE = "UNKNOWN_ENUM_VALUE", - - IN_PROGRESS = "IN_PROGRESS", - - FAILED = "FAILED", - - QUEUED = "QUEUED", - - TIMED_OUT = "TIMED_OUT", - - SUCCEEDED = "SUCCEEDED", - - CANCELED = "CANCELED", - - REJECTED = "REJECTED", - - REMOVED = "REMOVED", -} - /** * Data needed to make a DescribeJobExecution request. @@ -693,3 +693,42 @@ export interface UpdateJobExecutionSubscriptionRequest { } +/** + * Response document containing details about a failed request. + * + * @category IotJobs + */ +export interface V2ErrorResponse { + + /** + * Opaque token that can correlate this response to the original request. + * + */ + clientToken?: string; + + /** + * Indicates the type of error. + * + */ + code?: RejectedErrorCode; + + /** + * A text message that provides additional information. + * + */ + message?: string; + + /** + * The date and time the response was generated by AWS IoT. + * + */ + timestamp?: Date; + + /** + * A JobExecutionState object. This field is included only when the code field has the value InvalidStateTransition or VersionMismatch. + * + */ + executionState?: JobExecutionState; + +} + diff --git a/lib/iotjobs/v2utils.ts b/lib/iotjobs/v2utils.ts new file mode 100644 index 00000000..3e95a34d --- /dev/null +++ b/lib/iotjobs/v2utils.ts @@ -0,0 +1,464 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +import * as model from "./model"; +import {fromUtf8, toUtf8} from "@aws-sdk/util-utf8-browser"; +import * as model_validation_utils from "../mqtt_request_response_utils"; +import * as mqtt_request_response_utils from "../mqtt_request_response_utils"; +import {v4 as uuid} from "uuid"; + +function normalizeDescribeJobExecutionRequest(value: model.DescribeJobExecutionRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + if (value.executionNumber) { + normalizedValue.executionNumber = value.executionNumber; + } + if (value.includeJobDocument) { + normalizedValue.includeJobDocument = value.includeJobDocument; + } + + return normalizedValue; +} + +function buildDescribeJobExecutionRequestPayload(request: any) : ArrayBuffer { + let value = normalizeDescribeJobExecutionRequest(request as model.DescribeJobExecutionRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToDescribeJobExecutionRequest(request: any) : [any, string | undefined] { + let typedRequest: model.DescribeJobExecutionRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeGetPendingJobExecutionsRequest(value: model.GetPendingJobExecutionsRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + + return normalizedValue; +} + +function buildGetPendingJobExecutionsRequestPayload(request: any) : ArrayBuffer { + let value = normalizeGetPendingJobExecutionsRequest(request as model.GetPendingJobExecutionsRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToGetPendingJobExecutionsRequest(request: any) : [any, string | undefined] { + let typedRequest: model.GetPendingJobExecutionsRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeStartNextPendingJobExecutionRequest(value: model.StartNextPendingJobExecutionRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + if (value.stepTimeoutInMinutes) { + normalizedValue.stepTimeoutInMinutes = value.stepTimeoutInMinutes; + } + if (value.statusDetails) { + normalizedValue.statusDetails = value.statusDetails; + } + + return normalizedValue; +} + +function buildStartNextPendingJobExecutionRequestPayload(request: any) : ArrayBuffer { + let value = normalizeStartNextPendingJobExecutionRequest(request as model.StartNextPendingJobExecutionRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToStartNextPendingJobExecutionRequest(request: any) : [any, string | undefined] { + let typedRequest: model.StartNextPendingJobExecutionRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeUpdateJobExecutionRequest(value: model.UpdateJobExecutionRequest) : any { + let normalizedValue : any = {}; + + if (value.status) { + normalizedValue.status = value.status; + } + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + if (value.statusDetails) { + normalizedValue.statusDetails = value.statusDetails; + } + if (value.expectedVersion) { + normalizedValue.expectedVersion = value.expectedVersion; + } + if (value.executionNumber) { + normalizedValue.executionNumber = value.executionNumber; + } + if (value.includeJobExecutionState) { + normalizedValue.includeJobExecutionState = value.includeJobExecutionState; + } + if (value.includeJobDocument) { + normalizedValue.includeJobDocument = value.includeJobDocument; + } + if (value.stepTimeoutInMinutes) { + normalizedValue.stepTimeoutInMinutes = value.stepTimeoutInMinutes; + } + + return normalizedValue; +} + +function buildUpdateJobExecutionRequestPayload(request: any) : ArrayBuffer { + let value = normalizeUpdateJobExecutionRequest(request as model.UpdateJobExecutionRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToUpdateJobExecutionRequest(request: any) : [any, string | undefined] { + let typedRequest: model.UpdateJobExecutionRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function buildDescribeJobExecutionSubscriptions(request: any) : Array { + let typedRequest: model.DescribeJobExecutionRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/get/+`, + ); +} + +function buildDescribeJobExecutionPublishTopic(request: any) : string { + let typedRequest: model.DescribeJobExecutionRequest = request; + + return `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/get`; +} + +function buildDescribeJobExecutionResponsePaths(request: any) : Array { + let typedRequest: model.DescribeJobExecutionRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/get/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeDescribeJobExecutionResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/get/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildGetPendingJobExecutionsSubscriptions(request: any) : Array { + let typedRequest: model.GetPendingJobExecutionsRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/jobs/get/+`, + ); +} + +function buildGetPendingJobExecutionsPublishTopic(request: any) : string { + let typedRequest: model.GetPendingJobExecutionsRequest = request; + + return `$aws/things/${typedRequest.thingName}/jobs/get`; +} + +function buildGetPendingJobExecutionsResponsePaths(request: any) : Array { + let typedRequest: model.GetPendingJobExecutionsRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/jobs/get/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeGetPendingJobExecutionsResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/jobs/get/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildStartNextPendingJobExecutionSubscriptions(request: any) : Array { + let typedRequest: model.StartNextPendingJobExecutionRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/jobs/start-next/+`, + ); +} + +function buildStartNextPendingJobExecutionPublishTopic(request: any) : string { + let typedRequest: model.StartNextPendingJobExecutionRequest = request; + + return `$aws/things/${typedRequest.thingName}/jobs/start-next`; +} + +function buildStartNextPendingJobExecutionResponsePaths(request: any) : Array { + let typedRequest: model.StartNextPendingJobExecutionRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/jobs/start-next/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeStartNextJobExecutionResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/jobs/start-next/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildUpdateJobExecutionSubscriptions(request: any) : Array { + let typedRequest: model.UpdateJobExecutionRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/update/+`, + ); +} + +function buildUpdateJobExecutionPublishTopic(request: any) : string { + let typedRequest: model.UpdateJobExecutionRequest = request; + + return `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/update`; +} + +function buildUpdateJobExecutionResponsePaths(request: any) : Array { + let typedRequest: model.UpdateJobExecutionRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/update/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeUpdateJobExecutionResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/jobs/${typedRequest.jobId}/update/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function deserializeDescribeJobExecutionResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeGetPendingJobExecutionsResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeStartNextJobExecutionResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeUpdateJobExecutionResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeV2ErrorResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function createRequestResponseOperationServiceModelMap() : Map { + return new Map([ + ["describeJobExecution", { + inputShapeName: "DescribeJobExecutionRequest", + payloadTransformer: buildDescribeJobExecutionRequestPayload, + subscriptionGenerator: buildDescribeJobExecutionSubscriptions, + responsePathGenerator: buildDescribeJobExecutionResponsePaths, + publishTopicGenerator: buildDescribeJobExecutionPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToDescribeJobExecutionRequest, + }], + ["getPendingJobExecutions", { + inputShapeName: "GetPendingJobExecutionsRequest", + payloadTransformer: buildGetPendingJobExecutionsRequestPayload, + subscriptionGenerator: buildGetPendingJobExecutionsSubscriptions, + responsePathGenerator: buildGetPendingJobExecutionsResponsePaths, + publishTopicGenerator: buildGetPendingJobExecutionsPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToGetPendingJobExecutionsRequest, + }], + ["startNextPendingJobExecution", { + inputShapeName: "StartNextPendingJobExecutionRequest", + payloadTransformer: buildStartNextPendingJobExecutionRequestPayload, + subscriptionGenerator: buildStartNextPendingJobExecutionSubscriptions, + responsePathGenerator: buildStartNextPendingJobExecutionResponsePaths, + publishTopicGenerator: buildStartNextPendingJobExecutionPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToStartNextPendingJobExecutionRequest, + }], + ["updateJobExecution", { + inputShapeName: "UpdateJobExecutionRequest", + payloadTransformer: buildUpdateJobExecutionRequestPayload, + subscriptionGenerator: buildUpdateJobExecutionSubscriptions, + responsePathGenerator: buildUpdateJobExecutionResponsePaths, + publishTopicGenerator: buildUpdateJobExecutionPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToUpdateJobExecutionRequest, + }], + ]); +} + +function buildCreateJobExecutionsChangedStreamTopicFilter(config: any) : string { + const typedConfig : model.JobExecutionsChangedSubscriptionRequest = config; + + return `$aws/things/${typedConfig.thingName}/jobs/notify`; +} + +function buildCreateNextJobExecutionChangedStreamTopicFilter(config: any) : string { + const typedConfig : model.NextJobExecutionChangedSubscriptionRequest = config; + + return `$aws/things/${typedConfig.thingName}/jobs/notify-next`; +} + + +function deserializeJobExecutionsChangedEventPayload(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeNextJobExecutionChangedEventPayload(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + + +function createStreamingOperationServiceModelMap() : Map { + return new Map([ + ["createJobExecutionsChangedStream", { + inputShapeName: "JobExecutionsChangedSubscriptionRequest", + subscriptionGenerator: buildCreateJobExecutionsChangedStreamTopicFilter, + deserializer: deserializeJobExecutionsChangedEventPayload, + }], + ["createNextJobExecutionChangedStream", { + inputShapeName: "NextJobExecutionChangedSubscriptionRequest", + subscriptionGenerator: buildCreateNextJobExecutionChangedStreamTopicFilter, + deserializer: deserializeNextJobExecutionChangedEventPayload, + }], + ]); +} + +function validateDescribeJobExecutionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.DescribeJobExecutionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsTopicSegment(value.jobId, 'jobId'); + model_validation_utils.validateValueAsOptionalInteger(value.executionNumber, 'executionNumber'); + model_validation_utils.validateValueAsOptionalBoolean(value.includeJobDocument, 'includeJobDocument'); +} + +function validateGetPendingJobExecutionsRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.GetPendingJobExecutionsRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); +} + +function validateJobExecutionsChangedSubscriptionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.JobExecutionsChangedSubscriptionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); +} + +function validateNextJobExecutionChangedSubscriptionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.NextJobExecutionChangedSubscriptionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); +} + +function validateStartNextPendingJobExecutionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.StartNextPendingJobExecutionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsOptionalInteger(value.stepTimeoutInMinutes, 'stepTimeoutInMinutes'); + model_validation_utils.validateValueAsOptionalMap(value.statusDetails, model_validation_utils.validateValueAsString, model_validation_utils.validateValueAsString, 'statusDetails'); +} + +function validateUpdateJobExecutionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.UpdateJobExecutionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsTopicSegment(value.jobId, 'jobId'); + model_validation_utils.validateValueAsString(value.status, 'status'); + model_validation_utils.validateValueAsOptionalMap(value.statusDetails, model_validation_utils.validateValueAsString, model_validation_utils.validateValueAsString, 'statusDetails'); + model_validation_utils.validateValueAsOptionalInteger(value.expectedVersion, 'expectedVersion'); + model_validation_utils.validateValueAsOptionalInteger(value.executionNumber, 'executionNumber'); + model_validation_utils.validateValueAsOptionalBoolean(value.includeJobExecutionState, 'includeJobExecutionState'); + model_validation_utils.validateValueAsOptionalBoolean(value.includeJobDocument, 'includeJobDocument'); + model_validation_utils.validateValueAsOptionalInteger(value.stepTimeoutInMinutes, 'stepTimeoutInMinutes'); +} + + +function createValidatorMap() : Map void> { + return new Map void>([ + ["JobExecutionsChangedSubscriptionRequest", validateJobExecutionsChangedSubscriptionRequest], + ["NextJobExecutionChangedSubscriptionRequest", validateNextJobExecutionChangedSubscriptionRequest], + ["DescribeJobExecutionRequest", validateDescribeJobExecutionRequest], + ["GetPendingJobExecutionsRequest", validateGetPendingJobExecutionsRequest], + ["StartNextPendingJobExecutionRequest", validateStartNextPendingJobExecutionRequest], + ["UpdateJobExecutionRequest", validateUpdateJobExecutionRequest], + ]); +} + +export function makeServiceModel() : mqtt_request_response_utils.RequestResponseServiceModel { + let model : mqtt_request_response_utils.RequestResponseServiceModel = { + requestResponseOperations: createRequestResponseOperationServiceModelMap(), + streamingOperations: createStreamingOperationServiceModelMap(), + shapeValidators: createValidatorMap() + }; + + return model; +} diff --git a/lib/iotshadow/iotshadow.ts b/lib/iotshadow/iotshadow.ts new file mode 100644 index 00000000..c070bb18 --- /dev/null +++ b/lib/iotshadow/iotshadow.ts @@ -0,0 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +/** + * @packageDocumentation + * @module shadow + */ + +import * as model from "./model"; +import { IotShadowClient, IotShadowError } from "./iotshadowclient"; +import { IotShadowClientv2 } from "./iotshadowclientv2"; + +export { + IotShadowClient, + IotShadowClientv2, + IotShadowError, + model +}; diff --git a/lib/iotshadow/iotshadowclient.ts b/lib/iotshadow/iotshadowclient.ts index 430bb9bd..2899d573 100644 --- a/lib/iotshadow/iotshadowclient.ts +++ b/lib/iotshadow/iotshadowclient.ts @@ -15,8 +15,6 @@ import { mqtt, mqtt5 } from "aws-crt"; import { toUtf8 } from "@aws-sdk/util-utf8-browser" import * as service_client_mqtt_adapter from "../service_client_mqtt_adapter"; -export { model }; - /** * Error subclass for IotShadow service errors * @@ -79,7 +77,7 @@ export class IotShadowClient { * * @param client the MQTT5 client to use with this service client * - * @returns a new IotShadowClient instance + * @return a new IotShadowClient instance */ static newFromMqtt5Client(client: mqtt5.Mqtt5Client) : IotShadowClient { let serviceClient: IotShadowClient = new IotShadowClient(); @@ -97,7 +95,7 @@ export class IotShadowClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -126,7 +124,7 @@ export class IotShadowClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -154,7 +152,7 @@ export class IotShadowClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -183,7 +181,7 @@ export class IotShadowClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -211,7 +209,7 @@ export class IotShadowClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -240,7 +238,7 @@ export class IotShadowClient { * * @param request Message to be serialized and sent * @param qos Quality of Service for delivering this message - * @returns Promise which returns a `mqtt.MqttRequest` which will contain the packet id of + * @return Promise which returns a `mqtt.MqttRequest` which will contain the packet id of * the PUBLISH packet. * * * For QoS 0, completes as soon as the packet is sent. @@ -276,7 +274,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -325,7 +323,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -374,7 +372,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -422,7 +420,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -470,7 +468,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -519,7 +517,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -568,7 +566,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -616,7 +614,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -664,7 +662,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -713,7 +711,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -762,7 +760,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -810,7 +808,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -858,7 +856,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -907,7 +905,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -956,7 +954,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * @@ -1004,7 +1002,7 @@ export class IotShadowClient { * @param qos Maximum requested QoS that server may use when sending messages to the client. * The server may grant a lower QoS in the SUBACK * @param messageHandler Callback invoked when message or error is received from the server. - * @returns Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the + * @return Promise which returns a `mqtt.MqttSubscribeRequest` which will contain the * result of the SUBSCRIBE. The Promise resolves when a SUBACK is returned * from the server or is rejected when an exception occurs. * diff --git a/lib/iotshadow/iotshadowclientv2.spec.ts b/lib/iotshadow/iotshadowclientv2.spec.ts new file mode 100644 index 00000000..ba22d432 --- /dev/null +++ b/lib/iotshadow/iotshadowclientv2.spec.ts @@ -0,0 +1,414 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + + +import {iot, mqtt5, mqtt as mqtt311, mqtt_request_response} from "aws-crt"; +import {v4 as uuid} from "uuid"; +import {StreamingOperation} from "../mqtt_request_response"; +import {once} from "events"; +import {IotShadowClientv2} from "./iotshadowclientv2"; +import * as model from "./model"; + +jest.setTimeout(10000); + +function hasTestEnvironment() : boolean { + if (process.env.AWS_TEST_MQTT5_IOT_CORE_HOST === undefined) { + return false; + } + + if (process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH === undefined) { + return false; + } + + if (process.env.AWS_TEST_MQTT5_IOT_KEY_PATH === undefined) { + return false; + } + + return true; +} + +const conditional_test = (condition : boolean) => condition ? it : it.skip; + +function build_protocol_client_mqtt5() : mqtt5.Mqtt5Client { + let builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath( + // @ts-ignore + process.env.AWS_TEST_MQTT5_IOT_CORE_HOST, + process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH, + process.env.AWS_TEST_MQTT5_IOT_KEY_PATH + ); + + builder.withConnectProperties({ + clientId : `test-${uuid()}`, + keepAliveIntervalSeconds: 1200, + }); + + return new mqtt5.Mqtt5Client(builder.build()); +} + +function build_protocol_client_mqtt311() : mqtt311.MqttClientConnection { + // @ts-ignore + let builder = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder_from_path(process.env.AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH, process.env.AWS_TEST_MQTT5_IOT_KEY_PATH); + // @ts-ignore + builder.with_endpoint(process.env.AWS_TEST_MQTT5_IOT_CORE_HOST); + builder.with_client_id(`test-${uuid()}`); + + let client = new mqtt311.MqttClient(); + return client.new_connection(builder.build()); +} + +enum ProtocolVersion { + Mqtt311, + Mqtt5 +} + +interface TestingOptions { + version: ProtocolVersion, + timeoutSeconds?: number, +} + +class ShadowTestingContext { + + mqtt311Client?: mqtt311.MqttClientConnection; + mqtt5Client?: mqtt5.Mqtt5Client; + + client: IotShadowClientv2; + + private protocolStarted : boolean = false; + + async startProtocolClient() { + if (!this.protocolStarted) { + this.protocolStarted = true; + if (this.mqtt5Client) { + let connected = once(this.mqtt5Client, mqtt5.Mqtt5Client.CONNECTION_SUCCESS); + this.mqtt5Client.start(); + + await connected; + } + + if (this.mqtt311Client) { + await this.mqtt311Client.connect(); + } + } + } + + async stopProtocolClient() { + if (this.protocolStarted) { + this.protocolStarted = false; + if (this.mqtt5Client) { + let stopped = once(this.mqtt5Client, mqtt5.Mqtt5Client.STOPPED); + this.mqtt5Client.stop(); + await stopped; + + this.mqtt5Client.close(); + } + + if (this.mqtt311Client) { + await this.mqtt311Client.disconnect(); + } + } + } + + constructor(options: TestingOptions) { + if (options.version == ProtocolVersion.Mqtt5) { + this.mqtt5Client = build_protocol_client_mqtt5(); + + let rrOptions : mqtt_request_response.RequestResponseClientOptions = { + maxRequestResponseSubscriptions : 6, + maxStreamingSubscriptions : 2, + operationTimeoutInSeconds : options.timeoutSeconds ?? 60, + } + + this.client = IotShadowClientv2.newFromMqtt5(this.mqtt5Client, rrOptions); + } else { + this.mqtt311Client = build_protocol_client_mqtt311(); + + let rrOptions : mqtt_request_response.RequestResponseClientOptions = { + maxRequestResponseSubscriptions : 6, + maxStreamingSubscriptions : 2, + operationTimeoutInSeconds : options.timeoutSeconds ?? 60, + } + + this.client = IotShadowClientv2.newFromMqtt311(this.mqtt311Client, rrOptions); + } + } + + async open() { + await this.startProtocolClient(); + } + + async close() { + this.client.close(); + await this.stopProtocolClient(); + } +} + +async function doCreateDestroyTest(version: ProtocolVersion) { + let context = new ShadowTestingContext({ + version: version + }); + await context.open(); + + await context.close(); +} + +test('shadowv2 - create destroy mqtt5', async () => { + if (hasTestEnvironment()) { + await doCreateDestroyTest(ProtocolVersion.Mqtt5); + } +}); + +conditional_test(hasTestEnvironment())('shadowv2 - create destroy mqtt311', async () => { + await doCreateDestroyTest(ProtocolVersion.Mqtt311); +}); + +async function getNonexistentShadow(client: IotShadowClientv2, thingName: string, shadowName: string) { + let request : model.GetNamedShadowRequest = { + thingName: thingName, + shadowName: shadowName, + }; + + try { + await client.getNamedShadow(request); + expect(false); + } catch (err: any) { + expect(err.message).toContain("Request failed"); + expect(err.modeledError).toBeDefined(); + expect(err.modeledError.code).toEqual(404); + expect(err.modeledError.message).toContain("No shadow exists with name"); + } +} + +async function doGetNonexistentShadowTest(version: ProtocolVersion) { + let context = new ShadowTestingContext({ + version: version + }); + await context.open(); + + await getNonexistentShadow(context.client, uuid(), uuid()); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('shadowv2 - get non-existent shadow mqtt5', async () => { + await doGetNonexistentShadowTest(ProtocolVersion.Mqtt5); +}); + +conditional_test(hasTestEnvironment())('shadowv2 - get non-existent shadow mqtt311', async () => { + await doGetNonexistentShadowTest(ProtocolVersion.Mqtt311); +}); + +async function createShadow(client: IotShadowClientv2, thingName: string, shadowName: string, document: any) { + let request : model.UpdateNamedShadowRequest = { + thingName: thingName, + shadowName: shadowName, + state: { + desired: document, + reported: document, + } + }; + + try { + let response = await client.updateNamedShadow(request); + expect(response.state).toBeDefined(); + // @ts-ignore + expect(response.state.desired).toBeDefined(); + // @ts-ignore + expect(response.state.desired).toEqual(document); + // @ts-ignore + expect(response.state.reported).toBeDefined(); + // @ts-ignore + expect(response.state.reported).toEqual(document); + } catch (err) { + throw err; + } +} + +async function getShadow(client: IotShadowClientv2, thingName: string, shadowName: string, expectedDocument: any) { + let request : model.GetNamedShadowRequest = { + thingName: thingName, + shadowName: shadowName, + }; + + let response = await client.getNamedShadow(request); + expect(response.state).toBeDefined(); + // @ts-ignore + expect(response.state.desired).toBeDefined(); + // @ts-ignore + expect(response.state.desired).toEqual(expectedDocument); + // @ts-ignore + expect(response.state.reported).toBeDefined(); + // @ts-ignore + expect(response.state.reported).toEqual(expectedDocument); +} + +async function deleteShadow(client: IotShadowClientv2, thingName: string, shadowName: string, expectedVersion: number) { + let request : model.DeleteNamedShadowRequest = { + thingName: thingName, + shadowName: shadowName, + }; + + let response = await client.deleteNamedShadow(request); + expect(response.version).toEqual(expectedVersion); +} + +async function doCreateDeleteShadowTest(version: ProtocolVersion) { + let context = new ShadowTestingContext({ + version: version + }); + await context.open(); + + let thingName = uuid(); + let shadowName = uuid(); + let document = { + color: "green", + on: true, + }; + + // shouldn't exist yet + await getNonexistentShadow(context.client, thingName, shadowName); + + try { + await createShadow(context.client, thingName, shadowName, document); + + await getShadow(context.client, thingName, shadowName, document); + } finally { + await deleteShadow(context.client, thingName, shadowName, 1); + } + + await getNonexistentShadow(context.client, thingName, shadowName); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('shadowv2 - create-destroy shadow mqtt5', async () => { + await doCreateDeleteShadowTest(ProtocolVersion.Mqtt5); +}); + +conditional_test(hasTestEnvironment())('shadowv2 - create-destroy shadow mqtt311', async () => { + await doCreateDeleteShadowTest(ProtocolVersion.Mqtt311); +}); + +async function updateShadowDesired(client: IotShadowClientv2, thingName: string, shadowName: string, document: any) { + let request : model.UpdateNamedShadowRequest = { + thingName: thingName, + shadowName: shadowName, + state: { + desired: document, + }, + }; + + try { + let response = await client.updateNamedShadow(request); + expect(response.state).toBeDefined(); + // @ts-ignore + expect(response.state.desired).toBeDefined(); + // @ts-ignore + expect(response.state.desired).toEqual(document); + } catch (err) { + throw err; + } +} + +async function updateShadowReported(client: IotShadowClientv2, thingName: string, shadowName: string, document: any) { + let request : model.UpdateNamedShadowRequest = { + thingName: thingName, + shadowName: shadowName, + state: { + reported: document, + }, + }; + + try { + let response = await client.updateNamedShadow(request); + expect(response.state).toBeDefined(); + // @ts-ignore + expect(response.state.reported).toBeDefined(); + // @ts-ignore + expect(response.state.reported).toEqual(document); + } catch (err) { + throw err; + } +} + +async function doUpdateShadowTest(version: ProtocolVersion) { + let context = new ShadowTestingContext({ + version: version + }); + await context.open(); + + let thingName = uuid(); + let shadowName = uuid(); + let document = { + color: "green", + on: true, + }; + + let deltaEventStream = context.client.createNamedShadowDeltaUpdatedStream({ + thingName: thingName, + shadowName: shadowName, + }); + let deltaStatusUpdate = once(deltaEventStream, StreamingOperation.SUBSCRIPTION_STATUS); + deltaEventStream.open(); + let deltaStatus = (await deltaStatusUpdate)[0]; + expect(deltaStatus.type).toEqual(mqtt_request_response.SubscriptionStatusEventType.SubscriptionEstablished); + + let stateStream = context.client.createNamedShadowDeltaUpdatedStream({ + thingName: thingName, + shadowName: shadowName, + }); + let stateStatusUpdate = once(stateStream, StreamingOperation.SUBSCRIPTION_STATUS); + stateStream.open(); + let stateStatus = (await stateStatusUpdate)[0]; + expect(stateStatus.type).toEqual(mqtt_request_response.SubscriptionStatusEventType.SubscriptionEstablished); + + // shouldn't exist yet + await getNonexistentShadow(context.client, thingName, shadowName); + + try { + await createShadow(context.client, thingName, shadowName, document); + + await getShadow(context.client, thingName, shadowName, document); + + let updateDocument = { + color: "blue", + on: false, + }; + + let deltaEvent = once(deltaEventStream, StreamingOperation.INCOMING_PUBLISH); + let stateEvent = once(stateStream, StreamingOperation.INCOMING_PUBLISH); + + await updateShadowDesired(context.client, thingName, shadowName, updateDocument); + + let deltaResult = (await deltaEvent)[0]; + expect(deltaResult).toBeDefined(); + expect(deltaResult.message.state).toEqual(updateDocument); + + let stateResult = (await stateEvent)[0]; + expect(stateResult).toBeDefined(); + expect(stateResult.message.state).toEqual(updateDocument); + + await updateShadowReported(context.client, thingName, shadowName, updateDocument); + + await getShadow(context.client, thingName, shadowName, updateDocument); + + } finally { + deltaEventStream.close(); + stateStream.close(); + + await deleteShadow(context.client, thingName, shadowName, 3); + } + + await getNonexistentShadow(context.client, thingName, shadowName); + + await context.close(); +} + +conditional_test(hasTestEnvironment())('shadowv2 - update shadow mqtt5', async () => { + await doUpdateShadowTest(ProtocolVersion.Mqtt5); +}); + +conditional_test(hasTestEnvironment())('shadowv2 - update shadow mqtt311', async () => { + await doUpdateShadowTest(ProtocolVersion.Mqtt311); +}); \ No newline at end of file diff --git a/lib/iotshadow/iotshadowclientv2.ts b/lib/iotshadow/iotshadowclientv2.ts new file mode 100644 index 00000000..f1f1401e --- /dev/null +++ b/lib/iotshadow/iotshadowclientv2.ts @@ -0,0 +1,322 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +/** + * @packageDocumentation + * @module shadow + */ + +import {mqtt, mqtt5} from 'aws-crt'; +import {mqtt_request_response as mqtt_rr_internal} from 'aws-crt'; +import * as mqtt_request_response from '../mqtt_request_response'; +import * as mqtt_request_response_utils from '../mqtt_request_response_utils'; +import * as model from './model'; +import * as v2utils from './v2utils'; + +/** + * The AWS IoT Device Shadow service adds shadows to AWS IoT thing objects. Shadows are a simple data store for device properties and state. Shadows can make a device’s state available to apps and other services whether the device is connected to AWS IoT or not. + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html + * + * @category IotShadow + */ +export class IotShadowClientv2 { + private rrClient : mqtt_rr_internal.RequestResponseClient; + private serviceModel : mqtt_request_response_utils.RequestResponseServiceModel; + + private constructor(rrClient: mqtt_rr_internal.RequestResponseClient) { + this.rrClient = rrClient; + this.serviceModel = v2utils.makeServiceModel(); + } + + /** + * Creates a new service client that will use an SDK MQTT 311 client as transport. + * + * @param protocolClient the MQTT 311 client to use for transport + * @param options additional service client configuration options + * + * @return a new service client + * + */ + static newFromMqtt311(protocolClient: mqtt.MqttClientConnection, options: mqtt_rr_internal.RequestResponseClientOptions) : IotShadowClientv2 { + let rrClient = mqtt_rr_internal.RequestResponseClient.newFromMqtt311(protocolClient, options); + return new IotShadowClientv2(rrClient); + } + + /** + * Creates a new service client that will use an SDK MQTT 5 client as transport. + * + * @param protocolClient the MQTT 5 client to use for transport + * @param options additional service client configuration options + * + * @return a new service client + * + */ + static newFromMqtt5(protocolClient: mqtt5.Mqtt5Client, options: mqtt_rr_internal.RequestResponseClientOptions) : IotShadowClientv2 { + let rrClient = mqtt_rr_internal.RequestResponseClient.newFromMqtt5(protocolClient, options); + return new IotShadowClientv2(rrClient); + } + + /** + * Triggers cleanup of all resources associated with the service client. Closing a client will fail + * all incomplete requests and close all unclosed streaming operations. + * + * This must be called when finished with a client; otherwise, native resources will leak. + */ + close() { + this.rrClient.close(); + } + + /** + * Deletes a named shadow for an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#delete-pub-sub-topic + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotShadow + */ + async deleteNamedShadow(request: model.DeleteNamedShadowRequest) : Promise { + + let config = { + operationName: "deleteNamedShadow", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Deletes the (classic) shadow for an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#delete-pub-sub-topic + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotShadow + */ + async deleteShadow(request: model.DeleteShadowRequest) : Promise { + + let config = { + operationName: "deleteShadow", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Gets a named shadow for an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#get-pub-sub-topic + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotShadow + */ + async getNamedShadow(request: model.GetNamedShadowRequest) : Promise { + + let config = { + operationName: "getNamedShadow", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Gets the (classic) shadow for an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#get-pub-sub-topic + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotShadow + */ + async getShadow(request: model.GetShadowRequest) : Promise { + + let config = { + operationName: "getShadow", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Update a named shadow for a device. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-pub-sub-topic + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotShadow + */ + async updateNamedShadow(request: model.UpdateNamedShadowRequest) : Promise { + + let config = { + operationName: "updateNamedShadow", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Update a device's (classic) shadow. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-pub-sub-topic + * + * @param request operation to perform + * + * @return Promise which resolves into the response to the request + * + * @category IotShadow + */ + async updateShadow(request: model.UpdateShadowRequest) : Promise { + + let config = { + operationName: "updateShadow", + serviceModel: this.serviceModel, + client: this.rrClient, + request: request + }; + + return await mqtt_request_response_utils.doRequestResponse(config); + } + + /** + * Create a stream for NamedShadowDelta events for a named shadow of an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-delta-pub-sub-topic + * + * @param config streaming operation configuration options + * + * @return a streaming operation which will emit an event every time a message is received on the + * associated MQTT topic + * + * @category IotShadow + */ + createNamedShadowDeltaUpdatedStream(config: model.NamedShadowDeltaUpdatedSubscriptionRequest) + : mqtt_request_response.StreamingOperation { + + let streamingOperationConfig : mqtt_request_response_utils.StreamingOperationConfig = { + operationName: "createNamedShadowDeltaUpdatedStream", + serviceModel: this.serviceModel, + client: this.rrClient, + modelConfig: config, + }; + + return mqtt_request_response.StreamingOperation.create(streamingOperationConfig); + } + + /** + * Create a stream for ShadowUpdated events for a named shadow of an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-documents-pub-sub-topic + * + * @param config streaming operation configuration options + * + * @return a streaming operation which will emit an event every time a message is received on the + * associated MQTT topic + * + * @category IotShadow + */ + createNamedShadowUpdatedStream(config: model.NamedShadowUpdatedSubscriptionRequest) + : mqtt_request_response.StreamingOperation { + + let streamingOperationConfig : mqtt_request_response_utils.StreamingOperationConfig = { + operationName: "createNamedShadowUpdatedStream", + serviceModel: this.serviceModel, + client: this.rrClient, + modelConfig: config, + }; + + return mqtt_request_response.StreamingOperation.create(streamingOperationConfig); + } + + /** + * Create a stream for ShadowDelta events for the (classic) shadow of an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-delta-pub-sub-topic + * + * @param config streaming operation configuration options + * + * @return a streaming operation which will emit an event every time a message is received on the + * associated MQTT topic + * + * @category IotShadow + */ + createShadowDeltaUpdatedStream(config: model.ShadowDeltaUpdatedSubscriptionRequest) + : mqtt_request_response.StreamingOperation { + + let streamingOperationConfig : mqtt_request_response_utils.StreamingOperationConfig = { + operationName: "createShadowDeltaUpdatedStream", + serviceModel: this.serviceModel, + client: this.rrClient, + modelConfig: config, + }; + + return mqtt_request_response.StreamingOperation.create(streamingOperationConfig); + } + + /** + * Create a stream for ShadowUpdated events for the (classic) shadow of an AWS IoT thing. + * + * + * AWS documentation: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-documents-pub-sub-topic + * + * @param config streaming operation configuration options + * + * @return a streaming operation which will emit an event every time a message is received on the + * associated MQTT topic + * + * @category IotShadow + */ + createShadowUpdatedStream(config: model.ShadowUpdatedSubscriptionRequest) + : mqtt_request_response.StreamingOperation { + + let streamingOperationConfig : mqtt_request_response_utils.StreamingOperationConfig = { + operationName: "createShadowUpdatedStream", + serviceModel: this.serviceModel, + client: this.rrClient, + modelConfig: config, + }; + + return mqtt_request_response.StreamingOperation.create(streamingOperationConfig); + } + +} diff --git a/lib/iotshadow/model.ts b/lib/iotshadow/model.ts index 0900dd6e..2d26c6c8 100644 --- a/lib/iotshadow/model.ts +++ b/lib/iotshadow/model.ts @@ -654,3 +654,36 @@ export interface UpdateShadowSubscriptionRequest { } +/** + * Response document containing details about a failed request. + * + * @category IotShadow + */ +export interface V2ErrorResponse { + + /** + * Opaque request-response correlation data. Present only if a client token was used in the request. + * + */ + clientToken?: string; + + /** + * An HTTP response code that indicates the type of error. + * + */ + code?: number; + + /** + * A text message that provides additional information. + * + */ + message?: string; + + /** + * The date and time the response was generated by AWS IoT. This property is not present in all error response documents. + * + */ + timestamp?: Date; + +} + diff --git a/lib/iotshadow/v2utils.ts b/lib/iotshadow/v2utils.ts new file mode 100644 index 00000000..bc9f936c --- /dev/null +++ b/lib/iotshadow/v2utils.ts @@ -0,0 +1,632 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + * + * This file is generated + */ + +import * as model from "./model"; +import {fromUtf8, toUtf8} from "@aws-sdk/util-utf8-browser"; +import * as model_validation_utils from "../mqtt_request_response_utils"; +import * as mqtt_request_response_utils from "../mqtt_request_response_utils"; +import {v4 as uuid} from "uuid"; + +function normalizeDeleteNamedShadowRequest(value: model.DeleteNamedShadowRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + + return normalizedValue; +} + +function buildDeleteNamedShadowRequestPayload(request: any) : ArrayBuffer { + let value = normalizeDeleteNamedShadowRequest(request as model.DeleteNamedShadowRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToDeleteNamedShadowRequest(request: any) : [any, string | undefined] { + let typedRequest: model.DeleteNamedShadowRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeDeleteShadowRequest(value: model.DeleteShadowRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + + return normalizedValue; +} + +function buildDeleteShadowRequestPayload(request: any) : ArrayBuffer { + let value = normalizeDeleteShadowRequest(request as model.DeleteShadowRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToDeleteShadowRequest(request: any) : [any, string | undefined] { + let typedRequest: model.DeleteShadowRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeGetNamedShadowRequest(value: model.GetNamedShadowRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + + return normalizedValue; +} + +function buildGetNamedShadowRequestPayload(request: any) : ArrayBuffer { + let value = normalizeGetNamedShadowRequest(request as model.GetNamedShadowRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToGetNamedShadowRequest(request: any) : [any, string | undefined] { + let typedRequest: model.GetNamedShadowRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeGetShadowRequest(value: model.GetShadowRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + + return normalizedValue; +} + +function buildGetShadowRequestPayload(request: any) : ArrayBuffer { + let value = normalizeGetShadowRequest(request as model.GetShadowRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToGetShadowRequest(request: any) : [any, string | undefined] { + let typedRequest: model.GetShadowRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeUpdateNamedShadowRequest(value: model.UpdateNamedShadowRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + if (value.state) { + normalizedValue.state = value.state; + } + if (value.version) { + normalizedValue.version = value.version; + } + + return normalizedValue; +} + +function buildUpdateNamedShadowRequestPayload(request: any) : ArrayBuffer { + let value = normalizeUpdateNamedShadowRequest(request as model.UpdateNamedShadowRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToUpdateNamedShadowRequest(request: any) : [any, string | undefined] { + let typedRequest: model.UpdateNamedShadowRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function normalizeUpdateShadowRequest(value: model.UpdateShadowRequest) : any { + let normalizedValue : any = {}; + + if (value.clientToken) { + normalizedValue.clientToken = value.clientToken; + } + if (value.state) { + normalizedValue.state = value.state; + } + if (value.version) { + normalizedValue.version = value.version; + } + + return normalizedValue; +} + +function buildUpdateShadowRequestPayload(request: any) : ArrayBuffer { + let value = normalizeUpdateShadowRequest(request as model.UpdateShadowRequest); + + return fromUtf8(JSON.stringify(value)); +} + +function applyCorrelationTokenToUpdateShadowRequest(request: any) : [any, string | undefined] { + let typedRequest: model.UpdateShadowRequest = request; + + let correlationToken = uuid(); + + typedRequest.clientToken = correlationToken; + + return [typedRequest, correlationToken]; +} + +function buildDeleteNamedShadowSubscriptions(request: any) : Array { + let typedRequest: model.DeleteNamedShadowRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/delete/+`, + ); +} + +function buildDeleteNamedShadowPublishTopic(request: any) : string { + let typedRequest: model.DeleteNamedShadowRequest = request; + + return `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/delete`; +} + +function buildDeleteNamedShadowResponsePaths(request: any) : Array { + let typedRequest: model.DeleteNamedShadowRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/delete/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeDeleteShadowResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/delete/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildDeleteShadowSubscriptions(request: any) : Array { + let typedRequest: model.DeleteShadowRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/shadow/delete/+`, + ); +} + +function buildDeleteShadowPublishTopic(request: any) : string { + let typedRequest: model.DeleteShadowRequest = request; + + return `$aws/things/${typedRequest.thingName}/shadow/delete`; +} + +function buildDeleteShadowResponsePaths(request: any) : Array { + let typedRequest: model.DeleteShadowRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/shadow/delete/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeDeleteShadowResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/shadow/delete/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildGetNamedShadowSubscriptions(request: any) : Array { + let typedRequest: model.GetNamedShadowRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/get/+`, + ); +} + +function buildGetNamedShadowPublishTopic(request: any) : string { + let typedRequest: model.GetNamedShadowRequest = request; + + return `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/get`; +} + +function buildGetNamedShadowResponsePaths(request: any) : Array { + let typedRequest: model.GetNamedShadowRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/get/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeGetShadowResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/get/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildGetShadowSubscriptions(request: any) : Array { + let typedRequest: model.GetShadowRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/shadow/get/+`, + ); +} + +function buildGetShadowPublishTopic(request: any) : string { + let typedRequest: model.GetShadowRequest = request; + + return `$aws/things/${typedRequest.thingName}/shadow/get`; +} + +function buildGetShadowResponsePaths(request: any) : Array { + let typedRequest: model.GetShadowRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/shadow/get/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeGetShadowResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/shadow/get/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildUpdateNamedShadowSubscriptions(request: any) : Array { + let typedRequest: model.UpdateNamedShadowRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/update/accepted`, + `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/update/rejected`, + ); +} + +function buildUpdateNamedShadowPublishTopic(request: any) : string { + let typedRequest: model.UpdateNamedShadowRequest = request; + + return `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/update`; +} + +function buildUpdateNamedShadowResponsePaths(request: any) : Array { + let typedRequest: model.UpdateNamedShadowRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/update/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeUpdateShadowResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/shadow/name/${typedRequest.shadowName}/update/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function buildUpdateShadowSubscriptions(request: any) : Array { + let typedRequest: model.UpdateShadowRequest = request; + + return new Array( + `$aws/things/${typedRequest.thingName}/shadow/update/accepted`, + `$aws/things/${typedRequest.thingName}/shadow/update/rejected`, + ); +} + +function buildUpdateShadowPublishTopic(request: any) : string { + let typedRequest: model.UpdateShadowRequest = request; + + return `$aws/things/${typedRequest.thingName}/shadow/update`; +} + +function buildUpdateShadowResponsePaths(request: any) : Array { + let typedRequest: model.UpdateShadowRequest = request; + + return new Array( + { + topic: `$aws/things/${typedRequest.thingName}/shadow/update/accepted`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeUpdateShadowResponse, + }, + { + topic: `$aws/things/${typedRequest.thingName}/shadow/update/rejected`, + correlationTokenJsonPath: "clientToken", + deserializer: deserializeV2ErrorResponse, + }, + ); +} + +function deserializeDeleteShadowResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeGetShadowResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeUpdateShadowResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeV2ErrorResponse(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function createRequestResponseOperationServiceModelMap() : Map { + return new Map([ + ["deleteNamedShadow", { + inputShapeName: "DeleteNamedShadowRequest", + payloadTransformer: buildDeleteNamedShadowRequestPayload, + subscriptionGenerator: buildDeleteNamedShadowSubscriptions, + responsePathGenerator: buildDeleteNamedShadowResponsePaths, + publishTopicGenerator: buildDeleteNamedShadowPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToDeleteNamedShadowRequest, + }], + ["deleteShadow", { + inputShapeName: "DeleteShadowRequest", + payloadTransformer: buildDeleteShadowRequestPayload, + subscriptionGenerator: buildDeleteShadowSubscriptions, + responsePathGenerator: buildDeleteShadowResponsePaths, + publishTopicGenerator: buildDeleteShadowPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToDeleteShadowRequest, + }], + ["getNamedShadow", { + inputShapeName: "GetNamedShadowRequest", + payloadTransformer: buildGetNamedShadowRequestPayload, + subscriptionGenerator: buildGetNamedShadowSubscriptions, + responsePathGenerator: buildGetNamedShadowResponsePaths, + publishTopicGenerator: buildGetNamedShadowPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToGetNamedShadowRequest, + }], + ["getShadow", { + inputShapeName: "GetShadowRequest", + payloadTransformer: buildGetShadowRequestPayload, + subscriptionGenerator: buildGetShadowSubscriptions, + responsePathGenerator: buildGetShadowResponsePaths, + publishTopicGenerator: buildGetShadowPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToGetShadowRequest, + }], + ["updateNamedShadow", { + inputShapeName: "UpdateNamedShadowRequest", + payloadTransformer: buildUpdateNamedShadowRequestPayload, + subscriptionGenerator: buildUpdateNamedShadowSubscriptions, + responsePathGenerator: buildUpdateNamedShadowResponsePaths, + publishTopicGenerator: buildUpdateNamedShadowPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToUpdateNamedShadowRequest, + }], + ["updateShadow", { + inputShapeName: "UpdateShadowRequest", + payloadTransformer: buildUpdateShadowRequestPayload, + subscriptionGenerator: buildUpdateShadowSubscriptions, + responsePathGenerator: buildUpdateShadowResponsePaths, + publishTopicGenerator: buildUpdateShadowPublishTopic, + correlationTokenApplicator: applyCorrelationTokenToUpdateShadowRequest, + }], + ]); +} + +function buildCreateNamedShadowDeltaUpdatedStreamTopicFilter(config: any) : string { + const typedConfig : model.NamedShadowDeltaUpdatedSubscriptionRequest = config; + + return `$aws/things/${typedConfig.thingName}/shadow/name/${typedConfig.shadowName}/update/delta`; +} + +function buildCreateNamedShadowUpdatedStreamTopicFilter(config: any) : string { + const typedConfig : model.NamedShadowUpdatedSubscriptionRequest = config; + + return `$aws/things/${typedConfig.thingName}/shadow/name/${typedConfig.shadowName}/update/documents`; +} + +function buildCreateShadowDeltaUpdatedStreamTopicFilter(config: any) : string { + const typedConfig : model.ShadowDeltaUpdatedSubscriptionRequest = config; + + return `$aws/things/${typedConfig.thingName}/shadow/update/delta`; +} + +function buildCreateShadowUpdatedStreamTopicFilter(config: any) : string { + const typedConfig : model.ShadowUpdatedSubscriptionRequest = config; + + return `$aws/things/${typedConfig.thingName}/shadow/update/documents`; +} + + +function deserializeShadowDeltaUpdatedEventPayload(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + +function deserializeShadowUpdatedEventPayload(payload: ArrayBuffer) : any { + const payload_text = toUtf8(new Uint8Array(payload)); + + return JSON.parse(payload_text); +} + + +function createStreamingOperationServiceModelMap() : Map { + return new Map([ + ["createNamedShadowDeltaUpdatedStream", { + inputShapeName: "NamedShadowDeltaUpdatedSubscriptionRequest", + subscriptionGenerator: buildCreateNamedShadowDeltaUpdatedStreamTopicFilter, + deserializer: deserializeShadowDeltaUpdatedEventPayload, + }], + ["createNamedShadowUpdatedStream", { + inputShapeName: "NamedShadowUpdatedSubscriptionRequest", + subscriptionGenerator: buildCreateNamedShadowUpdatedStreamTopicFilter, + deserializer: deserializeShadowUpdatedEventPayload, + }], + ["createShadowDeltaUpdatedStream", { + inputShapeName: "ShadowDeltaUpdatedSubscriptionRequest", + subscriptionGenerator: buildCreateShadowDeltaUpdatedStreamTopicFilter, + deserializer: deserializeShadowDeltaUpdatedEventPayload, + }], + ["createShadowUpdatedStream", { + inputShapeName: "ShadowUpdatedSubscriptionRequest", + subscriptionGenerator: buildCreateShadowUpdatedStreamTopicFilter, + deserializer: deserializeShadowUpdatedEventPayload, + }], + ]); +} + +function validateDeleteNamedShadowRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.DeleteNamedShadowRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsTopicSegment(value.shadowName, 'shadowName'); +} + +function validateDeleteShadowRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.DeleteShadowRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); +} + +function validateGetNamedShadowRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.GetNamedShadowRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsTopicSegment(value.shadowName, 'shadowName'); +} + +function validateGetShadowRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.GetShadowRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); +} + +function validateNamedShadowDeltaUpdatedSubscriptionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.NamedShadowDeltaUpdatedSubscriptionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsTopicSegment(value.shadowName, 'shadowName'); +} + +function validateNamedShadowUpdatedSubscriptionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.NamedShadowUpdatedSubscriptionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsTopicSegment(value.shadowName, 'shadowName'); +} + +function validateShadowDeltaUpdatedSubscriptionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.ShadowDeltaUpdatedSubscriptionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); +} + +function validateShadowState(value: any) : void { + + // @ts-ignore + let typedValue : model.ShadowState = value; + + model_validation_utils.validateValueAsOptionalAny(value.desired, 'desired'); + model_validation_utils.validateValueAsOptionalAny(value.reported, 'reported'); +} + +function validateShadowUpdatedSubscriptionRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.ShadowUpdatedSubscriptionRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); +} + +function validateUpdateNamedShadowRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.UpdateNamedShadowRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsTopicSegment(value.shadowName, 'shadowName'); + model_validation_utils.validateValueAsOptionalObject(value.state, validateShadowState, 'state'); + model_validation_utils.validateValueAsOptionalInteger(value.version, 'version'); +} + +function validateUpdateShadowRequest(value: any) : void { + + // @ts-ignore + let typedValue : model.UpdateShadowRequest = value; + + model_validation_utils.validateValueAsTopicSegment(value.thingName, 'thingName'); + model_validation_utils.validateValueAsOptionalObject(value.state, validateShadowState, 'state'); + model_validation_utils.validateValueAsOptionalInteger(value.version, 'version'); +} + + +function createValidatorMap() : Map void> { + return new Map void>([ + ["NamedShadowDeltaUpdatedSubscriptionRequest", validateNamedShadowDeltaUpdatedSubscriptionRequest], + ["NamedShadowUpdatedSubscriptionRequest", validateNamedShadowUpdatedSubscriptionRequest], + ["ShadowDeltaUpdatedSubscriptionRequest", validateShadowDeltaUpdatedSubscriptionRequest], + ["ShadowUpdatedSubscriptionRequest", validateShadowUpdatedSubscriptionRequest], + ["DeleteNamedShadowRequest", validateDeleteNamedShadowRequest], + ["DeleteShadowRequest", validateDeleteShadowRequest], + ["GetNamedShadowRequest", validateGetNamedShadowRequest], + ["GetShadowRequest", validateGetShadowRequest], + ["UpdateNamedShadowRequest", validateUpdateNamedShadowRequest], + ["UpdateShadowRequest", validateUpdateShadowRequest], + ]); +} + +export function makeServiceModel() : mqtt_request_response_utils.RequestResponseServiceModel { + let model : mqtt_request_response_utils.RequestResponseServiceModel = { + requestResponseOperations: createRequestResponseOperationServiceModelMap(), + streamingOperations: createStreamingOperationServiceModelMap(), + shapeValidators: createValidatorMap() + }; + + return model; +} diff --git a/lib/mqtt_request_response.ts b/lib/mqtt_request_response.ts new file mode 100644 index 00000000..af81396c --- /dev/null +++ b/lib/mqtt_request_response.ts @@ -0,0 +1,177 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +import {EventEmitter} from "events"; +import {CrtError, mqtt_request_response} from "aws-crt"; +import * as mqtt_request_response_utils from "./mqtt_request_response_utils"; + +export interface IncomingPublishEvent { + message: T +} + +/** + * Signature for a handler that listens to incoming publish events. + */ +export type IncomingPublishListener = (eventData: IncomingPublishEvent) => void; + +export interface IncomingPublishErrorEvent { + payload: ArrayBuffer, + + error: ServiceError, +} + +/** + * Signature for a handler that listens to incoming publish error events + */ +export type IncomingPublishErrorListener = (eventData: IncomingPublishErrorEvent) => void; + +export type SubscriptionStatusEventListener = mqtt_request_response.SubscriptionStatusListener; + +export class StreamingOperation extends EventEmitter { + private operation: mqtt_request_response.StreamingOperationBase; + private deserializer: mqtt_request_response_utils.MessageDeserializer; + + private constructor(config: mqtt_request_response_utils.StreamingOperationConfig) { + super(); + + // validate + let streamingOperationModel = config.serviceModel.streamingOperations.get(config.operationName); + if (!streamingOperationModel) { + throw new CrtError("NYI"); + } + + let validator = config.serviceModel.shapeValidators.get(streamingOperationModel.inputShapeName); + if (!validator) { + throw new CrtError("NYI"); + } + + validator(config.modelConfig); + + let streamOptions : mqtt_request_response.StreamingOperationOptions = { + subscriptionTopicFilter: streamingOperationModel.subscriptionGenerator(config.modelConfig), + }; + + // create native operation + this.deserializer = streamingOperationModel.deserializer; + this.operation = config.client.createStream(streamOptions); + + this.operation.addListener(mqtt_request_response.StreamingOperationBase.SUBSCRIPTION_STATUS, this.onSubscriptionStatusChanged.bind(this)); + this.operation.addListener(mqtt_request_response.StreamingOperationBase.INCOMING_PUBLISH, this.onIncomingPublish.bind(this)); + } + + static create(config: mqtt_request_response_utils.StreamingOperationConfig) : StreamingOperation { + let operation = new StreamingOperation(config); + + return operation; + } + + open() { + this.operation.open(); + } + + close() { + this.operation.close(); + } + + /** + * Event emitted when the stream's subscription status changes. + * + * Listener type: {@link mqtt_request_response.SubscriptionStatusListener} + * + * @event + */ + static SUBSCRIPTION_STATUS : string = 'subscriptionStatus'; + + /** + * Event emitted when a stream message is received + * + * Listener type: {@link IncomingPublishListener} + * + * @event + */ + static INCOMING_PUBLISH : string = 'incomingPublish'; + + /** + * Event emitted when a stream message is received but handling it resulted in an error + * + * Listener type: {@link IncomingPublishErrorListener} + * + * @event + */ + static INCOMING_PUBLISH_ERROR : string = 'incomingPublishError'; + + on(event: 'subscriptionStatus', listener: mqtt_request_response.SubscriptionStatusListener): this; + + on(event: 'incomingPublish', listener: IncomingPublishListener): this; + + on(event: 'incomingPublishError', listener: IncomingPublishErrorListener): this; + + on(event: string | symbol, listener: (...args: any[]) => void): this { + super.on(event, listener); + return this; + } + + private onSubscriptionStatusChanged(eventData: mqtt_request_response.SubscriptionStatusEvent) : void { + setImmediate(async () => { + this.emit(StreamingOperation.SUBSCRIPTION_STATUS, eventData); + }) + } + + private onIncomingPublish(eventData: mqtt_request_response.IncomingPublishEvent) : void { + try { + let message = this.deserializer(eventData.payload); + + setImmediate(async () => { + this.emit(StreamingOperation.INCOMING_PUBLISH, { + message: message, + }); + }); + } catch (error) { + let serviceError = mqtt_request_response_utils.createServiceError((error as Error).toString()); + + setImmediate(async () => { + this.emit(StreamingOperation.INCOMING_PUBLISH_ERROR, { + payload: eventData.payload, + error: serviceError, + }); + }); + } + } +} + + +/** + * @internal + */ +interface ServiceErrorOptions { + description: string; + + internalError?: CrtError; + + modeledError?: any; +} + + +export class ServiceError extends Error { + + /** Optional inner/triggering error that can contain additional context. */ + readonly internalError?: CrtError; + + /** Optional service-specific modelled error data */ + readonly modeledError?: any; + + /** @internal */ + constructor(options: ServiceErrorOptions) { + super(options.description); + + if (options.internalError) { + this.internalError = options.internalError; + } + if (options.modeledError) { + this.modeledError = options.modeledError; + } + } +} + diff --git a/lib/mqtt_request_response_utils.ts b/lib/mqtt_request_response_utils.ts new file mode 100644 index 00000000..1e9d6e09 --- /dev/null +++ b/lib/mqtt_request_response_utils.ts @@ -0,0 +1,426 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +import {CrtError, mqtt_request_response} from "aws-crt"; +import {ServiceError} from "./mqtt_request_response"; + +export interface StreamingOperationConfig { + operationName: string, + + serviceModel: RequestResponseServiceModel, + + client: mqtt_request_response.RequestResponseClient, + + modelConfig: any, +} + +export interface RequestResponseOperationConfig { + operationName: string, + + serviceModel: RequestResponseServiceModel, + + client: mqtt_request_response.RequestResponseClient, + + request: any, +} + +export type MessageDeserializer = (payload: ArrayBuffer) => any; + +export interface RequestResponsePath { + topic: string, + + correlationTokenJsonPath?: string, + + deserializer: MessageDeserializer; +} + +export interface RequestResponseOperationModel { + inputShapeName: string; + + payloadTransformer: (request: any) => ArrayBuffer; + + subscriptionGenerator: (request: any) => Array; + + responsePathGenerator: (request: any) => Array; + + publishTopicGenerator: (request: any) => string; + + correlationTokenApplicator: (request: any) => [any, string | undefined]; +} + +export interface StreamingOperationModel { + inputShapeName: string; + + subscriptionGenerator: (config: any) => string; + + deserializer: MessageDeserializer; +} + +export interface RequestResponseServiceModel { + + // operation name -> operation model + requestResponseOperations: Map, + + // operation name -> operation model + streamingOperations: Map, + + // shape name -> validator function + shapeValidators: Map void>; +} + +function buildResponseDeserializerMap(paths: Array) : Map { + return new Map( + paths.map((path) => { + return [path.topic, path.deserializer]; + }) + ); +} + +function buildResponsePaths(paths: Array) : Array { + return paths.map((path) => { + let responsePath : mqtt_request_response.ResponsePath = { + topic: path.topic + }; + + if (path.correlationTokenJsonPath) { + responsePath.correlationTokenJsonPath = path.correlationTokenJsonPath; + } + + return responsePath; + }); +} + +export async function doRequestResponse(options: RequestResponseOperationConfig) : Promise { + return new Promise(async (resolve, reject) => { + try { + let operationModel = options.serviceModel.requestResponseOperations.get(options.operationName); + if (!operationModel) { + reject(createServiceError(`Operation "${options.operationName}" not in client's service model`)); + return; + } + + let validator = options.serviceModel.shapeValidators.get(operationModel.inputShapeName); + if (!validator) { + reject(createServiceError(`Operation "${options.operationName}" does not have an input validator`)); + return; + } + + validator(options.request); + + let publishTopic = operationModel.publishTopicGenerator(options.request); + let subscriptionsNeeded = operationModel.subscriptionGenerator(options.request); + let modelPaths = operationModel.responsePathGenerator(options.request); + let deserializerMap = buildResponseDeserializerMap(modelPaths); + let responsePaths = buildResponsePaths(modelPaths); + + let [request, correlationToken] = operationModel.correlationTokenApplicator(options.request); + + let payload = operationModel.payloadTransformer(request); + + let requestOptions: mqtt_request_response.RequestResponseOperationOptions = { + subscriptionTopicFilters: subscriptionsNeeded, + responsePaths: responsePaths, + publishTopic: publishTopic, + payload: payload + }; + + if (correlationToken) { + requestOptions.correlationToken = correlationToken; + } + + let response = await options.client.submitRequest(requestOptions); + + let responseTopic = response.topic; + let wasSuccess = responseTopic.endsWith("accepted"); // May need to eventually model + let responsePayload = response.payload; + + let deserializer = deserializerMap.get(responseTopic); + if (!deserializer) { + reject(createServiceError(`Operation "${options.operationName}" does not have a deserializer for topic "${responseTopic}"`)); + return; + } + + let deserializedResponse = deserializer(responsePayload) as ResponseType; + if (wasSuccess) { + resolve(deserializedResponse); + } else { + reject(createServiceError("Request failed", undefined, deserializedResponse)); + } + } catch (err) { + if (err instanceof ServiceError) { + reject(err); + } else if (err instanceof CrtError) { + reject(createServiceError("??", err as CrtError)); + } else { + reject(createServiceError((err as Error).toString())); + } + } + }); + + +} + +export function createServiceError(description: string, internalError?: CrtError, modeledError?: any) { + return new ServiceError({ + description: description, + internalError: internalError, + modeledError: modeledError, + }); +} + +function throwMissingPropertyError(propertyName?: string) : void { + if (propertyName) { + throw createServiceError(`validation failure - missing required property '${propertyName}'`); + } else { + throw createServiceError(`validation failure - missing required property`); + } +} + +function throwInvalidPropertyValueError(valueDescription: string, propertyName?: string) : void { + if (propertyName) { + throw createServiceError(`validation failure - property '${propertyName}' must be ${valueDescription}`); + } else { + throw createServiceError(`validation failure - property must be ${valueDescription}`); + } +} + +export function validateValueAsTopicSegment(value : any, propertyName?: string) : void { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + if (typeof value !== 'string') { + throwInvalidPropertyValueError("a string", propertyName); + } + + if (value.includes("/") || value.includes("#") || value.includes("+")) { + throwInvalidPropertyValueError("a valid MQTT topic", propertyName); + } +} + +export function validateOptionalValueAsNumber(value: any, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsNumber(value, propertyName); +} + +export function validateValueAsNumber(value: any, propertyName?: string) { + if (value == undefined) { + throwMissingPropertyError(propertyName); + } + + if (typeof value !== 'number') { + throwInvalidPropertyValueError("a number", propertyName); + } +} + +////////////// + +export function validateValueAsString(value : any, propertyName?: string) : void { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + if (typeof value !== 'string') { + throwInvalidPropertyValueError('a string value', propertyName); + } +} + +export function validateValueAsOptionalString(value : any, propertyName?: string) : void { + if (value === undefined) { + return; + } + + validateValueAsString(value, propertyName); +} + +export function validateValueAsInteger(value : any, propertyName?: string) { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + if (typeof value !== 'number' || !Number.isSafeInteger(value as number)) { + throwInvalidPropertyValueError('an integer value', propertyName); + } +} + +export function validateValueAsOptionalInteger(value : any, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsInteger(value, propertyName); +} + +export function validateValueAsBoolean(value : any, propertyName?: string) { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + if (typeof value !== 'boolean') { + throwInvalidPropertyValueError('a boolean value', propertyName); + } +} + +export function validateValueAsOptionalBoolean(value : any, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsBoolean(value, propertyName); +} + +export function validateValueAsDate(value : any, propertyName?: string) { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + if (!(value instanceof Date) || isNaN((value as Date).getTime())) { + throwInvalidPropertyValueError('a Date value', propertyName); + } +} + +export function validateValueAsOptionalDate(value : any, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsDate(value, propertyName); +} + +export function validateValueAsBlob(value : any, propertyName?: string) { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + /* there doesn't seem to be a good way of checking if something is an ArrayBuffer */ + if ((typeof value !== 'string') && !ArrayBuffer.isView(value) && (!value.byteLength || !value.maxByteLength)) { + throwInvalidPropertyValueError('a value convertible to a binary payload', propertyName); + } +} + +export function validateValueAsOptionalBlob(value : any, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsBlob(value, propertyName); +} + +export function validateValueAsAny(value : any, propertyName?: string) { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } +} + +export function validateValueAsOptionalAny(value : any, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsAny(value, propertyName); +} + +export type ElementValidator = (value : any) => void; + +export function validateValueAsArray(value : any, elementValidator : ElementValidator, propertyName?: string) { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + if (!Array.isArray(value)) { + throwInvalidPropertyValueError('an array value', propertyName); + } + + for (const element of value) { + try { + elementValidator(element); + } catch (err) { + let serviceError : ServiceError = err as ServiceError; + if (propertyName) { + throw createServiceError(`Array property '${propertyName}' contains an invalid value: ${serviceError.toString()}`); + } else { + throw createServiceError(`Array contains an invalid value: ${serviceError.toString()}`); + } + } + } +} + +export function validateValueAsOptionalArray(value : any, elementValidator : ElementValidator, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsArray(value, elementValidator, propertyName); +} + +export function validateValueAsMap(value : any, keyValidator : ElementValidator, valueValidator : ElementValidator, propertyName?: string) { + if (value === undefined) { + return; + } + + /* + if (!(value instanceof Map)) { + throwInvalidPropertyValueError('a map value', propertyName); + }*/ + + //let valueAsMap = value as Map; + for (const key in value) { + try { + keyValidator(key); + } catch (err) { + let serviceError : ServiceError = err as ServiceError; + if (propertyName) { + throw createServiceError(`Map property '${propertyName}' contains an invalid key: ${serviceError.toString()}`); + } else { + throw createServiceError(`Map contains an invalid key: ${serviceError.toString()}`); + } + } + + let val = value[key]; + try { + valueValidator(val); + } catch (err) { + let serviceError : ServiceError = err as ServiceError; + if (propertyName) { + throw createServiceError(`Map property '${propertyName}' contains an invalid value: ${serviceError.toString()}`); + } else { + throw createServiceError(`Map contains an invalid value: ${serviceError.toString()}`); + } + } + } +} + +export function validateValueAsOptionalMap(value : any, keyValidator : ElementValidator, valueValidator : ElementValidator, propertyName?: string) { + if (value === undefined) { + return; + } + + validateValueAsMap(value, keyValidator, valueValidator, propertyName); +} + +export function validateValueAsObject(value : any, elementValidator : ElementValidator, propertyName: string) { + if (value === undefined) { + throwMissingPropertyError(propertyName); + } + + try { + elementValidator(value); + } catch (err) { + let serviceError : ServiceError = err as ServiceError; + throw createServiceError(`Property '${propertyName}' contains an invalid value: ${serviceError.toString()}`); + } +} + +export function validateValueAsOptionalObject(value : any, elementValidator : ElementValidator, propertyName: string,) { + if (value === undefined) { + return; + } + + validateValueAsObject(value, elementValidator, propertyName); +} + diff --git a/package-lock.json b/package-lock.json index 89be0e4e..90f1c95f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,735 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "dev": true, + "requires": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "dev": true, + "requires": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dev": true, + "requires": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@aws-sdk/client-iot": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot/-/client-iot-3.556.0.tgz", + "integrity": "sha512-Cfi9VoaChP9A2SsjTldDVBUITs1tLnEgCD28Dn+j0eX8lt7tsB0nYeJV+O90TnbjqM0gRp284TDPZ+7OOQ4X6g==", + "dev": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.556.0", + "@aws-sdk/core": "3.556.0", + "@aws-sdk/credential-provider-node": "3.556.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true + } + } + }, + "@aws-sdk/client-sso": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.556.0.tgz", + "integrity": "sha512-unXdWS7uvHqCcOyC1de+Fr8m3F2vMg2m24GPea0bg7rVGTYmiyn9mhUX11VCt+ozydrw+F50FQwL6OqoqPocmw==", + "dev": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.556.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/client-sso-oidc": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.556.0.tgz", + "integrity": "sha512-AXKd2TB6nNrksu+OfmHl8uI07PdgzOo4o8AxoRO8SHlwoMAGvcT9optDGVSYoVfgOKTymCoE7h8/UoUfPc11wQ==", + "dev": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.556.0", + "@aws-sdk/core": "3.556.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/client-sts": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.556.0.tgz", + "integrity": "sha512-TsK3js7Suh9xEmC886aY+bv0KdLLYtzrcmVt6sJ/W6EnDXYQhBuKYFhp03NrN2+vSvMGpqJwR62DyfKe1G0QzQ==", + "dev": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.556.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/core": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.556.0.tgz", + "integrity": "sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA==", + "dev": true, + "requires": { + "@smithy/core": "^1.4.2", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.535.0.tgz", + "integrity": "sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.552.0.tgz", + "integrity": "sha512-vsmu7Cz1i45pFEqzVb4JcFmAmVnWFNLsGheZc8SCptlqCO5voETrZZILHYIl4cjKkSDk3pblBOf0PhyjqWW6WQ==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.556.0.tgz", + "integrity": "sha512-0Nz4ErOlXhe3muxWYMbPwRMgfKmVbBp36BAE2uv/z5wTbfdBkcgUwaflEvlKCLUTdHzuZsQk+BFS/gVyaUeOuA==", + "dev": true, + "requires": { + "@aws-sdk/client-sts": "3.556.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.556.0", + "@aws-sdk/credential-provider-web-identity": "3.556.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.556.0.tgz", + "integrity": "sha512-s1xVtKjyGc60O8qcNIzS1X3H+pWEwEfZ7TgNznVDNyuXvLrlNWiAcigPWGl2aAkc8tGcsSG0Qpyw2KYC939LFg==", + "dev": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.552.0", + "@aws-sdk/credential-provider-ini": "3.556.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.556.0", + "@aws-sdk/credential-provider-web-identity": "3.556.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz", + "integrity": "sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.556.0.tgz", + "integrity": "sha512-ETuBgcnpfxqadEAqhQFWpKoV1C/NAgvs5CbBc5EJbelJ8f4prTdErIHjrRtVT8c02MXj92QwczsiNYd5IoOqyw==", + "dev": true, + "requires": { + "@aws-sdk/client-sso": "3.556.0", + "@aws-sdk/token-providers": "3.556.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.556.0.tgz", + "integrity": "sha512-R/YAL8Uh8i+dzVjzMnbcWLIGeeRi2mioHVGnVF+minmaIkCiQMZg2HPrdlKm49El+RljT28Nl5YHRuiqzEIwMA==", + "dev": true, + "requires": { + "@aws-sdk/client-sts": "3.556.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.535.0.tgz", + "integrity": "sha512-0h6TWjBWtDaYwHMQJI9ulafeS4lLaw1vIxRjbpH0svFRt6Eve+Sy8NlVhECfTU2hNz/fLubvrUxsXoThaLBIew==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.535.0.tgz", + "integrity": "sha512-huNHpONOrEDrdRTvSQr1cJiRMNf0S52NDXtaPzdxiubTkP+vni2MohmZANMOai/qT0olmEVX01LhZ0ZAOgmg6A==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.535.0.tgz", + "integrity": "sha512-am2qgGs+gwqmR4wHLWpzlZ8PWhm4ktj5bYSgDrsOfjhdBlWNxvPoID9/pDAz5RWL48+oH7I6SQzMqxXsFDikrw==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.540.0.tgz", + "integrity": "sha512-8Rd6wPeXDnOYzWj1XCmOKcx/Q87L0K1/EHqOBocGjLVbN3gmRxBvpmR1pRTjf7IsWfnnzN5btqtcAkfDPYQUMQ==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.535.0.tgz", + "integrity": "sha512-IXOznDiaItBjsQy4Fil0kzX/J3HxIOknEphqHbOfUf+LpA5ugcsxuQQONrbEQusCBnfJyymrldBvBhFmtlU9Wg==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/token-providers": { + "version": "3.556.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.556.0.tgz", + "integrity": "sha512-tvIiugNF0/+2wfuImMrpKjXMx4nCnFWQjQvouObny+wrif/PGqqQYrybwxPJDvzbd965bu1I+QuSv85/ug7xsg==", + "dev": true, + "requires": { + "@aws-sdk/client-sso-oidc": "3.556.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/types": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.535.0.tgz", + "integrity": "sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.540.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.540.0.tgz", + "integrity": "sha512-1kMyQFAWx6f8alaI6UT65/5YW/7pDWAKAdNwL6vuJLea03KrZRX3PMoONOSJpAS5m3Ot7HlWZvf3wZDNTLELZw==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", + "@smithy/util-endpoints": "^1.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.535.0.tgz", + "integrity": "sha512-RWMcF/xV5n+nhaA/Ff5P3yNP3Kur/I+VNZngog4TEs92oB/nwOdAg/2JL8bVAhUbMrjTjpwm7PItziYFQoqyig==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/types": "^2.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.535.0.tgz", + "integrity": "sha512-dRek0zUuIT25wOWJlsRm97nTkUlh1NDcLsQZIN2Y8KxhwoXXWtJs5vaDPT+qAg+OpcNj80i1zLR/CirqlFg/TQ==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.535.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, "@aws-sdk/util-utf8-browser": { "version": "3.259.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", @@ -641,176 +1370,931 @@ "v8-to-istanbul": "^8.1.0" } }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "dev": true, + "requires": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "dev": true, + "requires": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + } + }, + "@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } + } + }, + "@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@smithy/abort-controller": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz", + "integrity": "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/config-resolver": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.2.0.tgz", + "integrity": "sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==", + "dev": true, + "requires": { + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/core": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.4.2.tgz", + "integrity": "sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA==", + "dev": true, + "requires": { + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/credential-provider-imds": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.3.0.tgz", + "integrity": "sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==", + "dev": true, + "requires": { + "@smithy/node-config-provider": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/fetch-http-handler": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz", + "integrity": "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==", + "dev": true, + "requires": { + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/hash-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.2.0.tgz", + "integrity": "sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/invalid-dependency": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.2.0.tgz", + "integrity": "sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/middleware-content-length": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.2.0.tgz", + "integrity": "sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==", + "dev": true, + "requires": { + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/middleware-endpoint": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz", + "integrity": "sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==", + "dev": true, + "requires": { + "@smithy/middleware-serde": "^2.3.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/middleware-retry": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.3.1.tgz", + "integrity": "sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==", + "dev": true, + "requires": { + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/service-error-classification": "^2.1.5", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true + } + } + }, + "@smithy/middleware-serde": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz", + "integrity": "sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/middleware-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz", + "integrity": "sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/node-config-provider": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz", + "integrity": "sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==", + "dev": true, + "requires": { + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/node-http-handler": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz", + "integrity": "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==", + "dev": true, + "requires": { + "@smithy/abort-controller": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/property-provider": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.2.0.tgz", + "integrity": "sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/protocol-http": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.3.0.tgz", + "integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/querystring-builder": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz", + "integrity": "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "@smithy/util-uri-escape": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/querystring-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz", + "integrity": "sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/service-error-classification": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.5.tgz", + "integrity": "sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz", + "integrity": "sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/signature-v4": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.3.0.tgz", + "integrity": "sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-hex-encoding": "^2.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-uri-escape": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/smithy-client": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.5.1.tgz", + "integrity": "sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==", + "dev": true, + "requires": { + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@smithy/url-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.2.0.tgz", + "integrity": "sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==", "dev": true, "requires": { - "@sinclair/typebox": "^0.27.8" + "@smithy/querystring-parser": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "@smithy/util-base64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.3.0.tgz", + "integrity": "sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==", "dev": true, "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "@smithy/util-body-length-browser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.2.0.tgz", + "integrity": "sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==", "dev": true, "requires": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "@smithy/util-body-length-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.3.0.tgz", + "integrity": "sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==", "dev": true, "requires": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "@smithy/util-config-provider": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.3.0.tgz", + "integrity": "sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "@smithy/util-defaults-mode-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.1.tgz", + "integrity": "sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@smithy/property-provider": "^2.2.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "@smithy/util-defaults-mode-node": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.1.tgz", + "integrity": "sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA==", + "dev": true, + "requires": { + "@smithy/config-resolver": "^2.2.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } }, - "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "@smithy/util-endpoints": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.2.0.tgz", + "integrity": "sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "dependencies": { - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true } } }, - "@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "@smithy/util-hex-encoding": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz", + "integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==", "dev": true, "requires": { - "@hapi/hoek": "^9.0.0" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true + "@smithy/util-middleware": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.2.0.tgz", + "integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==", + "dev": true, + "requires": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true + "@smithy/util-retry": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.2.0.tgz", + "integrity": "sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==", + "dev": true, + "requires": { + "@smithy/service-error-classification": "^2.1.5", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "@smithy/util-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.2.0.tgz", + "integrity": "sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==", + "dev": true, + "requires": { + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-hex-encoding": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } }, - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "@smithy/util-uri-escape": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz", + "integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==", "dev": true, "requires": { - "type-detect": "4.0.8" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, "@tootallnate/once": { @@ -904,9 +2388,9 @@ } }, "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" }, "@types/prettier": { "version": "2.7.2", @@ -929,6 +2413,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/uuid": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.13.tgz", + "integrity": "sha512-pAeZeUbLE4Z9Vi9wsWV2bYPTweEHeJJy0G4pEjOA/FSvy1Ad5U5Km8iDV6TKre1mjBiVNfAdVHKruP8bAh4Q5A==", + "dev": true + }, "@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -1226,6 +2716,12 @@ } } }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1814,6 +3310,15 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "dev": true, + "requires": { + "strnum": "^1.0.5" + } + }, "fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2278,8 +3783,7 @@ "isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "requires": {} + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==" }, "istanbul-lib-coverage": { "version": "3.2.0", @@ -2821,8 +4325,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} + "dev": true }, "jest-puppeteer": { "version": "10.0.1", @@ -3131,8 +4634,7 @@ "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -3446,8 +4948,7 @@ "ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "requires": {} + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==" } } }, @@ -3812,8 +5313,7 @@ "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -4097,21 +5597,6 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -4133,6 +5618,21 @@ "strip-ansi": "^6.0.1" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4160,6 +5660,12 @@ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true }, + "strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4426,8 +5932,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.1.0.tgz", "integrity": "sha512-jXH27L/wlxFjErgBXleh3opVgjVTXFEuBo68Yfl18S9Oh/IqxK6NV94jlEJ9hl4TXc9Zm2l7Rfk41CEkcCyvFQ==", - "dev": true, - "requires": {} + "dev": true }, "typescript": { "version": "4.9.5", @@ -4494,6 +5999,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", @@ -4652,8 +6162,7 @@ "ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "requires": {} + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 7c4ea06c..349f7cce 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,12 @@ "test:native": "npx jest --runInBand --verbose --config test/native/jest.config.js --forceExit" }, "devDependencies": { + "@aws-sdk/client-iot": "3.556.0", "@types/jest": "^27.0.1", - "@types/node": "^10.17.54", + "@types/node": "^14.18.63", "@types/puppeteer": "^5.4.7", "@types/ws": "8.5.4", + "@types/uuid": "^3.4.13", "cmake-js": "^7.3.0", "jest": "^27.2.1", "jest-puppeteer": "^10.0.1", @@ -43,6 +45,7 @@ }, "dependencies": { "@aws-sdk/util-utf8-browser": "^3.109.0", - "aws-crt": "1.27.3" + "aws-crt": "1.27.3", + "uuid": "^8.3.2" } } diff --git a/samples/browser/custom_authorizer_connect/package.json b/samples/browser/custom_authorizer_connect/package.json index 83f2a488..579b21da 100644 --- a/samples/browser/custom_authorizer_connect/package.json +++ b/samples/browser/custom_authorizer_connect/package.json @@ -1,5 +1,5 @@ { - "name": "pub_sub", + "name": "custom_authorizer_connect", "version": "1.0.0", "description": "Publish/Subscribe sample for AWS IoT Browser SDK", "homepage": "https://github.com/aws/aws-iot-device-sdk-js-v2", diff --git a/samples/browser/pub_sub_mqtt5/package.json b/samples/browser/pub_sub_mqtt5/package.json index eb320ac0..81b94bac 100644 --- a/samples/browser/pub_sub_mqtt5/package.json +++ b/samples/browser/pub_sub_mqtt5/package.json @@ -1,5 +1,5 @@ { - "name": "mqtt5", + "name": "pub_sub_mqtt5", "version": "1.0.0", "description": "Publish/Subscribe sample for AWS IoT Browser SDK", "homepage": "https://github.com/aws/aws-iot-device-sdk-js-v2", diff --git a/samples/browser/shared_subscription/package.json b/samples/browser/shared_subscription/package.json index 96bfb0d5..332f02dd 100644 --- a/samples/browser/shared_subscription/package.json +++ b/samples/browser/shared_subscription/package.json @@ -1,5 +1,5 @@ { - "name": "mqtt5", + "name": "shared_subscription", "version": "1.0.0", "description": "Shared Subscription sample for AWS IoT Browser SDK", "homepage": "https://github.com/aws/aws-iot-device-sdk-js-v2", diff --git a/samples/node/fleet_provisioning/README.md b/samples/node/deprecated/fleet_provisioning/README.md similarity index 99% rename from samples/node/fleet_provisioning/README.md rename to samples/node/deprecated/fleet_provisioning/README.md index 1372569f..aca35785 100644 --- a/samples/node/fleet_provisioning/README.md +++ b/samples/node/deprecated/fleet_provisioning/README.md @@ -1,6 +1,6 @@ # Node: Fleet provisioning -[**Return to main sample list**](../../README.md) +[**Return to main sample list**](../../../README.md) This sample uses the AWS IoT [Fleet provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html) to provision devices using either a CSR or Keys-And-Certificate and subsequently calls RegisterThing. This allows you to create new AWS IoT Core things using a Fleet Provisioning Template. diff --git a/samples/node/fleet_provisioning/index.ts b/samples/node/deprecated/fleet_provisioning/index.ts similarity index 99% rename from samples/node/fleet_provisioning/index.ts rename to samples/node/deprecated/fleet_provisioning/index.ts index 95d6a5b9..05b93b2b 100644 --- a/samples/node/fleet_provisioning/index.ts +++ b/samples/node/deprecated/fleet_provisioning/index.ts @@ -12,7 +12,7 @@ const yargs = require('yargs'); // The relative path is '../../util/cli_args' from here, but the compiled javascript file gets put one level // deeper inside the 'dist' folder -const common_args = require('../../../util/cli_args'); +const common_args = require('../../../../util/cli_args'); yargs.command('*', false, (yargs: any) => { common_args.add_direct_connection_establishment_arguments(yargs); diff --git a/samples/node/fleet_provisioning/package.json b/samples/node/deprecated/fleet_provisioning/package.json similarity index 93% rename from samples/node/fleet_provisioning/package.json rename to samples/node/deprecated/fleet_provisioning/package.json index cbab0aef..6246bf69 100644 --- a/samples/node/fleet_provisioning/package.json +++ b/samples/node/deprecated/fleet_provisioning/package.json @@ -21,7 +21,7 @@ "typescript": "^4.7.4" }, "dependencies": { - "aws-iot-device-sdk-v2": "file:../../..", + "aws-iot-device-sdk-v2": "file:../../../..", "yargs": "^16.2.0" } } diff --git a/samples/node/fleet_provisioning/tsconfig.json b/samples/node/deprecated/fleet_provisioning/tsconfig.json similarity index 100% rename from samples/node/fleet_provisioning/tsconfig.json rename to samples/node/deprecated/fleet_provisioning/tsconfig.json diff --git a/samples/node/deprecated/jobs/README.md b/samples/node/deprecated/jobs/README.md new file mode 100644 index 00000000..acfd8c5c --- /dev/null +++ b/samples/node/deprecated/jobs/README.md @@ -0,0 +1,134 @@ +# Node: Jobs + +[**Return to main sample list**](../../../README.md) + +This sample uses the AWS IoT [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) Service to describe jobs to execute. [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) is a service that allows you to define and respond to remote operation requests defined through the AWS IoT Core website or via any other device (or CLI command) that can access the [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) service. + +Note: This sample requires you to create jobs for your device to execute. See +[instructions here](https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html) for how to make jobs. + +On startup, the sample describes the jobs that are pending execution and pretends to process them, marking each job as complete as it does so. + +Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. + +
+Sample Policy +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "iot:Publish",
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/start-next",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/update",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/get",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/get"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Receive",
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/notify-next",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/start-next/*",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/update/*",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/get/*",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/get/*"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Subscribe",
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/notify-next",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/start-next/*",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/*/update/*",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/get/*",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/*/get/*"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Connect",
+      "Resource": "arn:aws:iot:region:account:client/test-*"
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. +* ``: The name of your AWS IoT Core thing you want the device connection to be associated with + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to run + +To run the Jobs sample, go to the `node/jobs` folder and run the following commands: + +``` sh +npm install +node dist/index --endpoint --cert --key --thing_name +``` + +You can also pass `--mqtt5` to run the sample with Mqtt5 Client +```sh +npm install +node dist/index --endpoint --cert --key --thing_name --mqtt5 +``` + +You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: + +``` sh +npm install +node dist/index --endpoint --cert --key --thing_name --ca_file +``` + +## Service Client Notes +### Differences between MQTT5 and MQTT311 +The service client with Mqtt5 client is almost identical to Mqtt3 one. The only difference is that you would need setup up a Mqtt5 Client and pass it to the service client. +For how to setup a Mqtt5 Client, please refer to [MQTT5 User Guide](https://github.com/awslabs/aws-crt-nodejs/blob/main/MQTT5-UserGuide.md) and [MQTT5 PubSub Sample](../pub_sub_mqtt5/README.md) + + + + + + + + + + +
Create a IotJobsClient with Mqtt5Create a IotJobsClient with Mqtt311
+ +```js + // Create a Mqtt5 Client + config_builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath(argv.endpoint, argv.cert, argv.key); + client = new mqtt5.Mqtt5Client(config_builder.build()); + + // Create the jobs client from Mqtt5 Client + jobs = iotjobs.IotJobsClient.newFromMqtt5Client(client5); +``` + + + +```js + // Create a Mqtt311 Connection from the command line data + config_builder = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder_from_path(argv.cert, argv.key); + config_builder.with_client_id(argv.client_id || "test-" + Math.floor(Math.random() * 100000000)); + config_builder.with_endpoint(argv.endpoint); + client = new mqtt.MqttClient(); + connection = client.new_connection(config); + + // Create the jobs client from Mqtt311 Connection + jobs = new iotjobs.IotJobsClient(connection); +``` + +
+ +### mqtt5.QoS v.s. mqtt.QoS +As the service client interface is unchanged for both Mqtt3 Connection and Mqtt5 Client,the service client will use mqtt.QoS instead of mqtt5.QoS even with a Mqtt5 Client. diff --git a/samples/node/deprecated/jobs/index.ts b/samples/node/deprecated/jobs/index.ts new file mode 100644 index 00000000..b1a6e400 --- /dev/null +++ b/samples/node/deprecated/jobs/index.ts @@ -0,0 +1,286 @@ +import { mqtt, iotjobs } from 'aws-iot-device-sdk-v2'; +import {once} from "events"; + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +type Args = { [index: string]: any }; +const yargs = require('yargs'); + +// The relative path is '../../util/cli_args' from here, but the compiled javascript file gets put one level +// deeper inside the 'dist' folder +const common_args = require('../../../../util/cli_args'); + +yargs.command('*', false, (yargs: any) => { + common_args.add_direct_connection_establishment_arguments(yargs); + common_args.add_jobs_arguments(yargs); +}, main).parse(); + +var available_jobs : Array = [] +var jobs_data = { + current_job_id: "", + current_execution_number: 0, + current_version_number: 0, +} + +async function on_rejected_error(error?: iotjobs.IotJobsError, response?:iotjobs.model.RejectedErrorResponse) { + if (error) { + console.log("Request rejected error: " + error); + } + console.log("Request rejected: " + response?.code + ": " + response?.message); + process.exit(1) +} + +async function on_get_pending_job_execution_accepted(error?: iotjobs.IotJobsError, response?: iotjobs.model.GetPendingJobExecutionsResponse) { + + if (error) { + console.log("Pending Jobs Error: " + error); + return; + } + + if (response) { + if (response.inProgressJobs || response.queuedJobs) { + console.log("Pending Jobs: ") + if (response.inProgressJobs) { + for (var i = 0; i < response.inProgressJobs.length; i++) { + var job_id = response.inProgressJobs[i].jobId; + var job_date = response.inProgressJobs[i].lastUpdatedAt; + if (typeof(job_date) == 'number') { + // Convert Epoch time format to a Javascript Date + job_date = new Date(job_date * 1000); + } + + if (job_id != undefined && job_date != undefined) { + available_jobs.push(job_id); + console.log(" In Progress: " + response.inProgressJobs[i].jobId + " @ " + job_date.toDateString()) + } + } + } + if (response.queuedJobs) { + for (var i = 0; i < response.queuedJobs.length; i++) { + var job_id = response.queuedJobs[i].jobId; + var job_date = response.queuedJobs[i].lastUpdatedAt; + if (typeof(job_date) == 'number') { + // Convert Epoch time format to a Javascript Date + job_date = new Date(job_date * 1000); + } + if (job_id != undefined && job_date != undefined) { + available_jobs.push(job_id); + console.log(" In Progress: " + response.queuedJobs[i].jobId + " @ " + job_date.toDateString()) + } + } + } + } + else { + console.log("Pending Jobs: None") + } + } +} + +async function on_describe_job_execution_accepted(error? : iotjobs.IotJobsError, response? : iotjobs.model.DescribeJobExecutionResponse) { + if (error) { + console.log("Describe Job Error: " + error); + return; + } + if (response) { + console.log("Describe Job: " + response.execution?.jobId + " version: " + response.execution?.versionNumber) + if (response.execution?.jobDocument) { + console.log(" Job document as JSON: " + JSON.stringify(response.execution.jobDocument, null, 2)) + } + // Print a new line to flush the console + console.log("\n"); + } +} + +async function on_start_next_pending_job_execution_accepted(error? : iotjobs.IotJobsError, response? : iotjobs.model.StartNextJobExecutionResponse) { + if (error) { + console.log("Start Job error: " + error); + return; + } + if (response) { + console.log("Start Job: " + response.execution?.jobId); + if (response.execution) { + if (response.execution.jobId) { + jobs_data.current_job_id = response.execution.jobId; + } + if (response.execution.executionNumber) { + jobs_data.current_execution_number = response.execution?.executionNumber; + } + if (response.execution.versionNumber) { + jobs_data.current_version_number = response.execution?.versionNumber; + } + } + } +} + +async function main(argv: Args) { + common_args.apply_sample_arguments(argv); + + var connection; + var client5; + var jobs; + + console.log("Connecting..."); + if (argv.mqtt5) { + client5 = common_args.build_mqtt5_client_from_cli_args(argv); + jobs = iotjobs.IotJobsClient.newFromMqtt5Client(client5); + + const connectionSuccess = once(client5, "connectionSuccess"); + client5.start(); + await connectionSuccess; + console.log("Connected with Mqtt5 Client!"); + } else { + connection = common_args.build_connection_from_cli_args(argv); + jobs = new iotjobs.IotJobsClient(connection); + + await connection.connect() + console.log("Connected with Mqtt3 Client!"); + } + + // Subscribe to necessary topics and get pending jobs + try { + + var pending_subscription_request : iotjobs.model.GetPendingJobExecutionsSubscriptionRequest = { + thingName: argv.thing_name + }; + await jobs.subscribeToGetPendingJobExecutionsAccepted(pending_subscription_request, mqtt.QoS.AtLeastOnce, on_get_pending_job_execution_accepted); + await jobs.subscribeToGetPendingJobExecutionsRejected(pending_subscription_request, mqtt.QoS.AtLeastOnce, on_rejected_error); + + var pending_publish_request : iotjobs.model.GetPendingJobExecutionsRequest = { + thingName: argv.thing_name + }; + await jobs.publishGetPendingJobExecutions(pending_publish_request, mqtt.QoS.AtLeastOnce); + + await sleep(500); // wait half a second + } catch (error) { + console.log(error); + process.exit(1) + } + + // Check if there are jobs to do + try { + if (available_jobs.length <= 0) { + console.log("No jobs queued, no further work to do"); + + if (argv.is_ci == true) { + console.log("ERROR: No jobs queued in CI! At least one job should be queued!"); + process.exit(1); + } + } + } catch (error) { + console.log(error); + process.exit(1) + } + + // Get descriptions of each job + try { + for (var i = 0; i < available_jobs.length; ++i) { + var description_subscription_request : iotjobs.model.DescribeJobExecutionRequest = { + thingName: argv.thing_name, + jobId: available_jobs[i] + } + await jobs.subscribeToDescribeJobExecutionAccepted(description_subscription_request, mqtt.QoS.AtLeastOnce, on_describe_job_execution_accepted); + await jobs.subscribeToDescribeJobExecutionRejected(description_subscription_request, mqtt.QoS.AtLeastOnce, on_rejected_error); + + var description_publish_request : iotjobs.model.DescribeJobExecutionRequest = { + thingName: argv.thing_name, + jobId: available_jobs[i], + includeJobDocument: true, + executionNumber: 1 + } + await jobs.publishDescribeJobExecution(description_publish_request, mqtt.QoS.AtLeastOnce); + } + } catch (error) { + console.log(error); + process.exit(-1) + } + + // Wait a half second before starting to process each job + await sleep(500); + + // Pretend to do each job + try { + if (argv.is_ci == false) { + for (var job_idx = 0; job_idx < available_jobs.length; ++job_idx) { + + // Start the next pending job + // ================================================== + var start_next_subscription_request : iotjobs.model.StartNextPendingJobExecutionSubscriptionRequest = { + thingName: argv.thing_name + } + + await jobs.subscribeToStartNextPendingJobExecutionAccepted(start_next_subscription_request, mqtt.QoS.AtLeastOnce, on_start_next_pending_job_execution_accepted); + await jobs.subscribeToStartNextPendingJobExecutionRejected(start_next_subscription_request, mqtt.QoS.AtLeastOnce, on_rejected_error); + + var start_next_publish_request : iotjobs.model.StartNextPendingJobExecutionRequest = { + thingName: argv.thing_name, + stepTimeoutInMinutes: 15 + } + await jobs.publishStartNextPendingJobExecution(start_next_publish_request, mqtt.QoS.AtLeastOnce); + // ================================================== + + // Update the service to let it know we're executing + // ================================================== + var executing_subscription_request : iotjobs.model.UpdateJobExecutionSubscriptionRequest = { + thingName: argv.thing_name, + jobId: jobs_data.current_job_id + } + await jobs.subscribeToUpdateJobExecutionAccepted(executing_subscription_request, mqtt.QoS.AtLeastOnce, + (error?: iotjobs.IotJobsError, response?: iotjobs.model.UpdateJobExecutionResponse) => { + console.log("Marked job " + jobs_data.current_job_id + " IN_PROGRESS"); + }); + await jobs.subscribeToUpdateJobExecutionRejected(executing_subscription_request, mqtt.QoS.AtLeastOnce, on_rejected_error); + + var executing_publish_request : iotjobs.model.UpdateJobExecutionRequest = { + thingName: argv.thing_name, + jobId: jobs_data.current_job_id, + executionNumber: jobs_data.current_execution_number, + status: iotjobs.model.JobStatus.IN_PROGRESS, + expectedVersion: jobs_data.current_version_number++ + }; + await jobs.publishUpdateJobExecution(executing_publish_request, mqtt.QoS.AtLeastOnce); + // ================================================== + + // Fake doing something + await sleep(argv.job_time * 1000); + + // Update the service to let it know we are done + // ================================================== + var done_subscription_request : iotjobs.model.UpdateJobExecutionSubscriptionRequest = { + thingName: argv.thing_name, + jobId: jobs_data.current_job_id + } + await jobs.subscribeToUpdateJobExecutionAccepted(done_subscription_request, mqtt.QoS.AtLeastOnce, + (error?: iotjobs.IotJobsError, response?: iotjobs.model.UpdateJobExecutionResponse) => { + console.log("Marked job " + jobs_data.current_job_id + " SUCCEEDED"); + }); + await jobs.subscribeToUpdateJobExecutionRejected(done_subscription_request, mqtt.QoS.AtLeastOnce, on_rejected_error); + + var done_publish_request : iotjobs.model.UpdateJobExecutionRequest = { + thingName: argv.thing_name, + jobId: jobs_data.current_job_id, + executionNumber: jobs_data.current_execution_number, + status: iotjobs.model.JobStatus.SUCCEEDED, + expectedVersion: jobs_data.current_version_number++ + }; + await jobs.publishUpdateJobExecution(done_publish_request, mqtt.QoS.AtLeastOnce); + // ================================================== + } + } + } catch (error) { + console.log(error); + process.exit(-1) + } + + console.log("Disconnecting..."); + if (connection) { + await connection.disconnect(); + } else { + let stopped = once(client5, "stopped"); + client5.stop(); + await stopped; + client5.close(); + } + console.log("Disconnected"); + // Quit NodeJS + process.exit(0); +} diff --git a/samples/node/deprecated/jobs/package.json b/samples/node/deprecated/jobs/package.json new file mode 100644 index 00000000..04b559fc --- /dev/null +++ b/samples/node/deprecated/jobs/package.json @@ -0,0 +1,27 @@ +{ + "name": "jobs", + "version": "1.0.0", + "description": "NodeJS IoT SDK v2 Jobs Sample", + "homepage": "https://github.com/aws/aws-iot-device-sdk-js-v2", + "repository": { + "type": "git", + "url": "git+https://github.com/aws/aws-iot-device-sdk-js-v2.git" + }, + "contributors": [ + "AWS SDK Common Runtime Team " + ], + "license": "Apache-2.0", + "main": "./dist/index.js", + "scripts": { + "tsc": "tsc", + "prepare": "npm run tsc" + }, + "devDependencies": { + "@types/node": "^10.17.50", + "typescript": "^4.7.4" + }, + "dependencies": { + "aws-iot-device-sdk-v2": "../../../../", + "yargs": "^16.2.0" + } +} diff --git a/samples/node/deprecated/jobs/tsconfig.json b/samples/node/deprecated/jobs/tsconfig.json new file mode 100644 index 00000000..92617173 --- /dev/null +++ b/samples/node/deprecated/jobs/tsconfig.json @@ -0,0 +1,62 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "removeComments": false, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + }, + "include": [ + "*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/samples/node/deprecated/shadow/README.md b/samples/node/deprecated/shadow/README.md new file mode 100644 index 00000000..824e0f40 --- /dev/null +++ b/samples/node/deprecated/shadow/README.md @@ -0,0 +1,138 @@ +# Node: Shadow + +[**Return to main sample list**](../../../README.md) + +This sample uses the AWS IoT [Device Shadow](https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html) Service to keep a property in sync between device and server. Imagine a light whose color may be changed through an app, or set by a local user. + +Once connected, type a value in the terminal and press Enter to update the property's "reported" value. The sample also responds when the "desired" value changes on the server. To observe this, edit the Shadow document in the AWS Console and set a new "desired" value. + +On startup, the sample requests the shadow document to learn the property's initial state. The sample also subscribes to "delta" events from the server, which are sent when a property's "desired" value differs from its "reported" value. When the sample learns of a new desired value, that value is changed on the device and an update is sent to the server with the new "reported" value. + +Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. + +
+Sample Policy +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Publish"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/get",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/get/accepted",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/get/rejected",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update/accepted",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update/rejected",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update/delta"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/get/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/get/rejected",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/update/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/update/rejected",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/update/delta"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Connect",
+      "Resource": "arn:aws:iot:region:account:client/test-*"
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. +* ``: The name of your AWS IoT Core thing you want the device connection to be associated with + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to run + +To run the Shadow sample, go to the `node/shadow` folder and run the following commands: + +``` sh +npm install +node dist/index.js --endpoint --cert --key --thing_name --shadow_property +``` + +You can also pass `--mqtt5` to run the sample with Mqtt5 Client +```sh +npm install +node dist/index.js --endpoint --cert --key --thing_name --shadow_property --mqtt5 +``` + +You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: + +``` sh +npm install +node dist/index.js --endpoint --cert --key --thing_name --shadow_property --ca_file +``` + + +## Service Client Notes +### Differences between MQTT5 and MQTT311 +The service client with Mqtt5 client is almost identical to Mqtt3 one. The only difference is that you would need setup up a Mqtt5 Client and pass it to the service client. +For how to setup a Mqtt5 Client, please refer to [MQTT5 User Guide](https://github.com/awslabs/aws-crt-nodejs/blob/main/MQTT5-UserGuide.md) and [MQTT5 PubSub Sample](../pub_sub_mqtt5/README.md) + + + + + + + + + + +
Create a IoTShadowClient with Mqtt5Create a IoTShadowClient with Mqtt311
+ +```js + // Create a Mqtt5 Client + config_builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath(argv.endpoint, argv.cert, argv.key); + client = new mqtt5.Mqtt5Client(config_builder.build()); + + // Create the shadow client from Mqtt5 Client + shadow = iotshadow.IotShadowClient.newFromMqtt5Client(client5); +``` + + + +```js + // Create a Mqtt311 Connection from the command line data + config_builder = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder_from_path(argv.cert, argv.key); + config_builder.with_client_id(argv.client_id || "test-" + Math.floor(Math.random() * 100000000)); + config_builder.with_endpoint(argv.endpoint); + client = new mqtt.MqttClient(); + connection = client.new_connection(config); + + // Create the shadow client from Mqtt311 Connection + shadow = new iotshadow.IotShadowClient(connection); +``` + +
+ +### mqtt5.QoS v.s. mqtt.QoS +As the service client interface is unchanged for both Mqtt3 Connection and Mqtt5 Client,the service client will use mqtt.QoS instead of mqtt5.QoS even with a Mqtt5 Client. diff --git a/samples/node/deprecated/shadow/index.ts b/samples/node/deprecated/shadow/index.ts new file mode 100644 index 00000000..d2b316fa --- /dev/null +++ b/samples/node/deprecated/shadow/index.ts @@ -0,0 +1,399 @@ +import { mqtt, iotshadow } from 'aws-iot-device-sdk-v2'; +import { stringify } from 'querystring'; +import readline from 'readline'; +import {once} from "events"; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); +const prompt = (query: string) => new Promise((resolve) => rl.question(query, resolve)); +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +type Args = { [index: string]: any }; +const yargs = require('yargs'); + +// The relative path is '../../util/cli_args' from here, but the compiled javascript file gets put one level +// deeper inside the 'dist' folder +const common_args = require('../../../../util/cli_args'); + +var shadow_value: unknown; +var shadow_property: string; + +var shadow_update_complete = false; + +yargs.command('*', false, (yargs: any) => { + common_args.add_direct_connection_establishment_arguments(yargs); + common_args.add_shadow_arguments(yargs); +}, main).parse(); + +async function sub_to_shadow_update(shadow: iotshadow.IotShadowClient, argv: Args) { + return new Promise(async (resolve, reject) => { + try { + function updateAccepted(error?: iotshadow.IotShadowError, response?: iotshadow.model.UpdateShadowResponse) { + if (response) { + + if (response.clientToken !== undefined) { + console.log("Succcessfully updated shadow for clientToken: " + response.clientToken + "."); + } + else { + console.log("Succcessfully updated shadow."); + } + if (response.state?.desired !== undefined) { + console.log("\t desired state: " + stringify(response.state.desired)); + } + if (response.state?.reported !== undefined) { + console.log("\t reported state: " + stringify(response.state.reported)); + } + } + + if (error || !response) { + console.log("Updated shadow is missing the target property."); + } + resolve(true); + } + + function updateRejected(error?: iotshadow.IotShadowError, response?: iotshadow.model.ErrorResponse) { + if (response) { + console.log("Update request was rejected."); + } + + if (error) { + console.log("Error occurred..") + } + reject(error); + } + + console.log("Subscribing to Update events.."); + const updateShadowSubRequest: iotshadow.model.UpdateNamedShadowSubscriptionRequest = { + shadowName: argv.shadow_property, + thingName: argv.thing_name + }; + + await shadow.subscribeToUpdateShadowAccepted( + updateShadowSubRequest, + mqtt.QoS.AtLeastOnce, + (error, response) => updateAccepted(error, response)); + + await shadow.subscribeToUpdateShadowRejected( + updateShadowSubRequest, + mqtt.QoS.AtLeastOnce, + (error, response) => updateRejected(error, response)); + + resolve(true); + } + catch (error) { + reject(error); + } + }); +} + +async function sub_to_shadow_get(shadow: iotshadow.IotShadowClient, argv: Args) { + return new Promise(async (resolve, reject) => { + try { + function getAccepted(error?: iotshadow.IotShadowError, response?: iotshadow.model.GetShadowResponse) { + + if (response?.state) { + if (response?.state.delta) { + const value = response.state.delta; + if (value) { + console.log("Shadow contains delta value '" + stringify(value) + "'."); + change_shadow_value(shadow, argv, value); + } + } + + if (response?.state.reported) { + const value_any: any = response.state.reported; + if (value_any) { + let found_property = false; + for (var prop in value_any) { + if (prop === shadow_property) { + found_property = true; + console.log("Shadow contains '" + prop + "'. Reported value: '" + String(value_any[prop]) + "'."); + break; + } + } + if (found_property === false) { + console.log("Shadow does not contain '" + shadow_property + "' property."); + } + } + } + } + + if (error || !response) { + console.log("Error occurred.."); + } + shadow_update_complete = true; + resolve(true); + } + + function getRejected(error?: iotshadow.IotShadowError, response?: iotshadow.model.ErrorResponse) { + + if (response) { + console.log("In getRejected response."); + } + + if (error) { + console.log("Error occurred.."); + } + + shadow_update_complete = true; + reject(error); + } + + console.log("Subscribing to Get events.."); + const getShadowSubRequest: iotshadow.model.GetShadowSubscriptionRequest = { + thingName: argv.thing_name + }; + + await shadow.subscribeToGetShadowAccepted( + getShadowSubRequest, + mqtt.QoS.AtLeastOnce, + (error, response) => getAccepted(error, response)); + + await shadow.subscribeToGetShadowRejected( + getShadowSubRequest, + mqtt.QoS.AtLeastOnce, + (error, response) => getRejected(error, response)); + + resolve(true); + } + catch (error) { + reject(error); + } + }); +} + +async function sub_to_shadow_delta(shadow: iotshadow.IotShadowClient, argv: Args) { + return new Promise(async (resolve, reject) => { + try { + function deltaEvent(error?: iotshadow.IotShadowError, response?: iotshadow.model.GetShadowResponse) { + console.log("\nReceived shadow delta event."); + + if (response?.clientToken != null) { + console.log(" ClientToken: " + response.clientToken); + } + + if (response?.state !== null) { + let value_any: any = response?.state; + if (value_any === null || value_any === undefined) { + console.log("Delta reports that '" + shadow_property + "' was deleted. Resetting defaults.."); + let data_to_send: any = {}; + data_to_send[shadow_property] = argv.shadow_value; + change_shadow_value(shadow, argv, data_to_send); + } + else { + if (value_any[shadow_property] !== undefined) { + if (value_any[shadow_property] !== shadow_value) { + console.log("Delta reports that desired value is '" + value_any[shadow_property] + "'. Changing local value.."); + let data_to_send: any = {}; + data_to_send[shadow_property] = value_any[shadow_property]; + change_shadow_value(shadow, argv, data_to_send); + } + else { + console.log("Delta did not report a change in '" + shadow_property + "'."); + } + } + else { + console.log("Desired value not found in delta. Skipping.."); + } + } + } + else { + console.log("Delta did not report a change in '" + shadow_property + "'."); + } + + resolve(true); + } + + console.log("Subscribing to Delta events.."); + const deltaShadowSubRequest: iotshadow.model.ShadowDeltaUpdatedSubscriptionRequest = { + thingName: argv.thing_name + }; + + await shadow.subscribeToShadowDeltaUpdatedEvents( + deltaShadowSubRequest, + mqtt.QoS.AtLeastOnce, + (error, response) => deltaEvent(error, response)); + + resolve(true); + } + catch (error) { + reject(error); + } + }); +} + +async function get_current_shadow(shadow: iotshadow.IotShadowClient, argv: Args) { + return new Promise(async (resolve, reject) => { + try { + const getShadow: iotshadow.model.GetShadowRequest = { + thingName: argv.thing_name + } + + shadow_update_complete = false; + console.log("Requesting current shadow state.."); + shadow.publishGetShadow( + getShadow, + mqtt.QoS.AtLeastOnce); + + await get_current_shadow_update_wait(); + resolve(true); + } + catch (error) { + reject(error); + } + }); +} + + +async function get_current_shadow_update_wait() { + // Wait until shadow_update_complete is true, showing the result returned + return await new Promise(resolve => { + const interval = setInterval(() => { + if (shadow_update_complete == true) { + resolve(true); + clearInterval(interval); + }; + }, 200); + }); +} + +function change_shadow_value(shadow: iotshadow.IotShadowClient, argv: Args, new_value?: object) { + return new Promise(async (resolve, reject) => { + try { + if (typeof new_value !== 'undefined') { + let new_value_any: any = new_value; + let skip_send = false; + + if (new_value_any !== null) { + if (new_value_any[shadow_property] == shadow_value) { + skip_send = true; + } + } + if (skip_send == false) { + if (new_value_any === null) { + shadow_value = new_value_any; + } + else { + shadow_value = new_value_any[shadow_property]; + } + + console.log("Changed local shadow value to '" + shadow_value + "'."); + + var updateShadow: iotshadow.model.UpdateShadowRequest = { + state: { + desired: new_value, + reported: new_value + }, + thingName: argv.thing_name + }; + + await shadow.publishUpdateShadow( + updateShadow, + mqtt.QoS.AtLeastOnce) + + console.log("Update request published."); + } + } + } + catch (error) { + console.log("Failed to publish update request.") + reject(error); + } + resolve(true) + }); +} + +async function main(argv: Args) { + common_args.apply_sample_arguments(argv); + + shadow_property = argv.shadow_property; + + var connection; + var client5; + var shadow; + + console.log("Connecting..."); + if (argv.mqtt5) { // Build the mqtt5 client + client5 = common_args.build_mqtt5_client_from_cli_args(argv); + shadow = iotshadow.IotShadowClient.newFromMqtt5Client(client5); + + const connectionSuccess = once(client5, "connectionSuccess"); + client5.start(); + await connectionSuccess; + console.log("Connected with Mqtt5 Client..."); + } else { // Build the mqtt3 based connection + connection = common_args.build_connection_from_cli_args(argv); + shadow = new iotshadow.IotShadowClient(connection); + + await connection.connect(); + console.log("Connected with Mqtt3 Client..."); + } + + try { + await sub_to_shadow_update(shadow, argv); + await sub_to_shadow_get(shadow, argv); + await sub_to_shadow_delta(shadow, argv); + await get_current_shadow(shadow, argv); + + await sleep(500); // wait half a second + + // Take console input when this sample is not running in CI + if (argv.is_ci == false) { + while (true) { + const userInput = await prompt("Enter desired value: "); + if (userInput === "quit") { + break; + } + else { + let data_to_send: any = {}; + + if (userInput == "clear_shadow") { + data_to_send = null; + } + else if (userInput == "null") { + data_to_send[shadow_property] = null; + } + else { + data_to_send[shadow_property] = userInput; + } + + await change_shadow_value(shadow, argv, data_to_send); + await get_current_shadow(shadow, argv); + } + } + } + // If this is running in CI, then automatically update the shadow + else { + var messages_sent = 0; + while (messages_sent < 5) { + let data_to_send: any = {} + data_to_send[shadow_property] = "Shadow_Value_" + messages_sent.toString() + await change_shadow_value(shadow, argv, data_to_send); + await get_current_shadow(shadow, argv); + messages_sent += 1 + } + } + + } catch (error) { + console.log(error); + } + + console.log("Disconnecting.."); + + if (connection) { + await connection.disconnect(); + } else { + let stopped = once(client5, "stopped"); + client5.stop(); + await stopped; + client5.close(); + } + + // force node to wait a second before quitting to finish any promises + await sleep(1000); + console.log("Disconnected"); + // Quit NodeJS + process.exit(0); +} diff --git a/samples/node/deprecated/shadow/package.json b/samples/node/deprecated/shadow/package.json new file mode 100644 index 00000000..30d0e7f8 --- /dev/null +++ b/samples/node/deprecated/shadow/package.json @@ -0,0 +1,27 @@ +{ + "name": "shadow", + "version": "1.0.0", + "description": "NodeJS IoT SDK v2 Shadow Sample", + "homepage": "https://github.com/aws/aws-iot-device-sdk-js-v2", + "repository": { + "type": "git", + "url": "git+https://github.com/aws/aws-iot-device-sdk-js-v2.git" + }, + "contributors": [ + "AWS SDK Common Runtime Team " + ], + "license": "Apache-2.0", + "main": "./dist/index.js", + "scripts": { + "tsc": "tsc", + "prepare": "npm run tsc" + }, + "devDependencies": { + "@types/node": "^10.17.50", + "typescript": "^4.7.4" + }, + "dependencies": { + "aws-iot-device-sdk-v2": "../../../../", + "yargs": "^16.2.0" + } +} diff --git a/samples/node/deprecated/shadow/tsconfig.json b/samples/node/deprecated/shadow/tsconfig.json new file mode 100644 index 00000000..92617173 --- /dev/null +++ b/samples/node/deprecated/shadow/tsconfig.json @@ -0,0 +1,62 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "removeComments": false, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + }, + "include": [ + "*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/samples/node/fleet_provisioning/basic/README.md b/samples/node/fleet_provisioning/basic/README.md new file mode 100644 index 00000000..c08c59a5 --- /dev/null +++ b/samples/node/fleet_provisioning/basic/README.md @@ -0,0 +1,265 @@ +# Node: Fleet provisioning + +[**Return to main sample list**](../../README.md) + +This sample uses the AWS IoT [Fleet provisioning service](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html) to provision devices using the CreateKeysAndCertificate and RegisterThing APIs. This allows you to create new AWS IoT Core thing resources using a Fleet Provisioning Template. + +The [IAM Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) attached to your provisioning certificate must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used that will allow this sample to run as intended. + +
+(see sample policy) +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "iot:Publish",
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/certificates/create/json",
+        "arn:aws:iot:region:account:topic/$aws/provisioning-templates/templatename/provision/json"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/certificates/create/json/accepted",
+        "arn:aws:iot:region:account:topic/$aws/certificates/create/json/rejected",
+        "arn:aws:iot:region:account:topic/$aws/provisioning-templates/templatename/provision/json/accepted",
+        "arn:aws:iot:region:account:topic/$aws/provisioning-templates/templatename/provision/json/rejected"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/$aws/certificates/create/json/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/certificates/create/json/rejected",
+        "arn:aws:iot:region:account:topicfilter/$aws/provisioning-templates/templatename/provision/json/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/provisioning-templates/templatename/provision/json/rejected"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Connect",
+      "Resource": "arn:aws:iot:region:account:client/test-*"
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. +* ``: The name of your AWS Fleet Provisioning template you want to use to create new AWS IoT Core Things. + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +### How to run + +There are many different ways to run the Fleet Provisioning sample because of how many different ways there are to setup a Fleet Provisioning template in AWS IoT Core. **The easiest and most common way is to run the sample with the following**: + +``` sh +# from the node/fleet_provisioning folder +npm install +node ./index.js --endpoint --cert --key --template_name