title: Why is my AWS IoT SDK thing shadow update request timing out using the Node SDK?
body: <p>Following an <a href="https://github.com/aws/aws-iot-device-sdk-js/blob/master/examples/thing-example.js" rel="nofollow noreferrer">AWS example here</a> and referencing a <a href="https://github.com/balena-io-playground/balena-aws-device" rel="nofollow noreferrer">balena.io example</a>, I'm attempting to to get a "thing" (currently a script on my Mac) to update a thing shadow on AWS. </p>
  
  <p>I'm getting close. So far I can successfully register interest in a thing shadow (UPDATE: and subscribe and publish to an MQTT topic, receiving updtes). However, I'm getting a timeout when attempting to update the shadow. Originally, I was running into a timeout when registering interest due to a missing policy on the thing's certificate, now a basic one in place. My current thought is that maybe I need to use a different root CA cert (currently using CA1 provided) or maybe something is wrong with my base64 encoded cert strings, encoded with:</p>
  
  <pre><code>openssl base64 -in some-cert.pem -out some-cert.txt
  #gets copied to clipboard and pasted in UI env field
  pbcopy &lt; some-cert.txt
  </code></pre>
  
  <p>Here's what I have so far. Please mind the TODO notes as this is a work-in-progress.</p>
  
  <p>Policy (over-permissive for now):</p>
  
  <pre><code>{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": "iot:*",
        "Resource": "*"
      }
    ]
  }
  </code></pre>
  
  <p>troubleshooting.ts</p>
  
  <pre><code>// application endpoint
  const AWS_ENDPOINT = "my-endpoint.iot.us-east-1.amazonaws.com";
  //  thing private key encoded in base64
  const AWS_PRIVATE_CERT = "gets set in balena.io UI as base64 string";
  // aws root CA1 certificate encoded in base64
  const AWS_ROOT_CERT = "gets set in balena.io UI as base64 string";
  // thing certificate
  const AWS_THING_CERT = "gets set in balena.io UI as base64 string";
  const AWS_REGION = 'us-east-1';
  
  import { DataUploader } from './data-uploader';
  import { DataUploaderOptions } from './model/data-uploader-options';
  
  const dataUploaderOptions: DataUploaderOptions = {
    awsEndpoint: AWS_ENDPOINT,
    awsPrivateCert: AWS_PRIVATE_CERT,
    awsCaCert: AWS_ROOT_CERT,
    awsThingCert: AWS_THING_CERT,
    awsRegion: AWS_REGION
  }
  
  const dataUploader = new DataUploader(dataUploaderOptions);
  </code></pre>
  
  <p>data-uploader.ts</p>
  
  <pre><code>/**
   * Upload image and meta data to AWS as an AWS IoT thing.
   */
  
  import { thingShadow, ThingShadowOptions } from 'aws-iot-device-sdk';
  import { DataUploaderOptions } from './model/data-uploader-options';
  import { get } from 'lodash';
  
  export class DataUploader {
  
    // Provided options via constructor parameter
    protected readonly options: DataUploaderOptions;
  
    // AWS IoT thing shadows
    protected thingShadows: thingShadow;
    protected thingName = 'TwinTigerSecurityCamera';
    protected currentTimeout: NodeJS.Timer;
    // TODO: Typescript wants MqttClient type, better than 'any', check if it makes sense to import here just for type reference.
    protected clientToken: string | void | any;
  
    // TEMP: Move to class options:
    protected operationTimeout = 10000;
    protected operationTimeoutRetry = 1000;
  
    constructor(options: DataUploaderOptions) {
      // Set options based on input or defaults if not provided.
      this.options = {
        awsEndpoint: get(options, 'awsEndpoint', ''), // empty will throw an error
        awsPrivateCert: get(options, 'awsPrivateCert', ''), // empty will throw an error
        awsCaCert: get(options, 'awsCaCert', ''), // empty will throw an error
        awsThingCert: get(options, 'awsThingCert', ''), // empty will throw an error
        awsRegion: get(options, 'awsRegion', ''), // empty will throw an error
        awsMqttClientId: get(options, 'awsMqttClientId', this.createUniqueClientId())
      };
  
      // Proceed no further if AWS options are not set properly.
      if (!this.options.awsEndpoint ||
          !this.options.awsPrivateCert ||
          !this.options.awsCaCert ||
          !this.options.awsThingCert ||
          !this.options.awsRegion) {
        throw new Error('DataUploader constructor: AWS IoT options are required.');
      }
  
      // setup thingShadow and events
      this.initThingShadow();
    }
  
    /**
     * 
     */
    protected initThingShadow = () =&gt; {
      // create thing shadow: extends IoT 'device' with extra status that can be
      // set/received online or offline (can sync when online)
      const thingShadowOptions: ThingShadowOptions = {
        // TODO: revise 'ca' to 'rootCa1' to help clarify
        clientCert: Buffer.from(this.options.awsThingCert, 'base64'),
        caCert: Buffer.from(this.options.awsCaCert, 'base64'),
        privateKey: Buffer.from(this.options.awsPrivateCert, 'base64'),
        clientId: this.options.awsMqttClientId,
        host: this.options.awsEndpoint,
        region: this.options.awsRegion
      };
      this.thingShadows = new thingShadow(thingShadowOptions);
  
      this.thingShadows.on('connect', () =&gt; {
        console.log('connected');
        this.registerThingShadowInterest();
      });
  
      // Report the status of update(), get(), and delete() calls.
      this.thingShadows.on('status', (thingName, stat, clientToken, stateObject) =&gt; {
        const tmpObj = JSON.stringify(stateObject);
        console.log(`received ${stat} on ${thingName}: ${tmpObj}`);
      });
  
      this.thingShadows.on('message', (topic, message) =&gt; {
        const tmpObj = JSON.stringify(message); 
        console.log(`message received for ${topic}: ${tmpObj}`);
      });
  
      this.thingShadows.on('foreignStateChange', (thingName, operation, stateObject) =&gt; {
        const tmpObj = JSON.stringify(stateObject); 
        console.log(`foreignStateChange happened for ${thingName}, ${operation}: ${tmpObj}`);
      });
  
      this.thingShadows.on('delta', (thingName, stateObject) =&gt; {
        const tmpObj = JSON.stringify(stateObject); 
        console.log(`received delta on ${thingName}: ${tmpObj}`);
      });
  
      this.thingShadows.on('timeout', (thingName, clientToken) =&gt; {
        console.log(`timeout for ${thingName}: ${clientToken}`);
      });
    }
  
    /**
     * 
     */
    protected setInitialThingShadowState = (): void =&gt; {
      // TODO: consider making interface for this
      const cameraState = JSON.stringify({
        state: {
          desired: {
            signedUrlRequests: 10,
            signedUrls: [],
            startedAt: new Date(),
            uploadCount: 0,
            uploadSpeed: 0
          }
        }
      });
  
      this.thingShadowOperation('update', cameraState);
    }
  
    /**
     * 
     */
    protected registerThingShadowInterest = () =&gt; {
      this.thingShadows.register(this.thingName, { 
        ignoreDeltas: true
      },
      (err, failedTopics) =&gt; {
        if (!err &amp;&amp; !failedTopics) {
          console.log(`${this.thingName} interest is registered.`);
          this.setInitialThingShadowState();
        } else {
          // TODO: What do we do now? Throw an error?
          const failedString = JSON.stringify(failedTopics);
          console.error(`registerThingShadowInterest error occurred: ${err.message}, failed topics: ${failedString}`);
        }
      });
    }
  
    /**
     * Thanks: https://github.com/aws/aws-iot-device-sdk-js/blob/master/examples/thing-example.js
     */
    protected thingShadowOperation = (operation: string, state: Object) =&gt; {
      // TODO: Check if there's a better way to do this. We want to accept operation
      // parameter as string only (no any), then ensure it's a key of the thingShadow
      // class. It works in TypeScipt, however calling class methods dynamically seems
      // like one of the few cases when the developer wants to ditch TypeScript.
      const operationKey: ('register' | 'unregister' | 'update' | 'get' | 'delete' | 'publish' | 'subscribe' | 'unsubscribe') = &lt;any&gt;operation;
      const clientToken = this.thingShadows[operationKey](this.thingName, state);
  
      if (clientToken === null) {
         // The thing shadow operation can't be performed because another one
         // is pending. If no other operation is pending, reschedule it after an 
         // interval which is greater than the thing shadow operation timeout.
         if (this.currentTimeout !== null) {
            console.log('Operation in progress, scheduling retry...');
            this.currentTimeout = setTimeout(
               function() {
                  this.thingShadowOperation(operation, state);
               },
               this.operationTimeout + this.operationTimeoutRetry);
         }
      } else {
         // Save the client token so that we know when the operation completes.
         this.clientToken = clientToken;
      }
   }
  
    /**
     * Generate a unique MQTT client id so not to collide with other ids in use.
     */
    // TODO
    createUniqueClientId = (): string =&gt; {
      return 'temporaryClientIdWillBeMoreUniqueInTheFuture';
    }
  }
  </code></pre>
  
  <p>model/data-uploader-options.ts</p>
  
  <pre><code>// DataUploader options
  export interface DataUploaderOptions {
    // AWS IoT endpoint found in IoT settings in management console
    awsEndpoint: string;
  
    // AWS IoT private certificate for single device
    awsPrivateCert: string;
  
    // AWS IoT CA certificate
    awsCaCert: string;
  
    // AWS IoT thing certificate for single device
    awsThingCert: string;
  
    // AWS IoT region where thing settings live
    awsRegion: string;
  
    // an MQTT client id that needs to be unique amongst all other client ids
    awsMqttClientId?: string;
  }
  </code></pre>
  
  <p>What am I missing to update the thing shadow?</p>
  
  <hr>
  
  <p><strong>Update:</strong></p>
  
  <p>When I use the certificates directly and not base64 string versions, I get the same timeout results. Example:</p>
  
  <pre><code>    const thingShadowOptions = {
        keyPath: '/Users/me/Downloads/private-key-aaaaaaaaaa-private.pem.key',
        certPath: '/Users/me/Downloads/thing-aaaaaa-certificate.pem.crt',
        caPath: '/Users/me/Downloads/ca-AmazonRootCA1.pem',
        clientId: this.options.awsMqttClientId,
        host: this.options.awsEndpoint,
        region: this.options.awsRegion
      }
  </code></pre>
  
  <p>Also, I am able to subscribe to an MQTT topic and publish to it:</p>
  
  <pre><code>    this.thingShadows.on('connect', () =&gt; {
        console.log('connected');
        // this.registerThingShadowInterest();
  
        // TEMP
        this.thingShadows.subscribe('topic_1');
        this.thingShadows.publish('topic_1', JSON.stringify({ test_data: 1}));
      });
  </code></pre>
  
  <p>Outputs:</p>
  
  <pre><code>message received for topic_1: {"type":"Buffer","data":[123,34,116,101,115,116,95,100,97,116,97,34,58,49,125]}
  </code></pre>