I am making a Cloudflare Worker that Tweets from my Twitter Developer APIs whenever I make a blank GET request to the worker. At first you might think “that’s easy just use npm’s twitter-api-v2,” but that won’t work because Cloudflare workers need to be in pure javascript/typescript and can’t rely on any external modules. So I attempted to do this with the following code.

index.ts

import { handleRequest } from './handler'

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request))
})

handler.ts

import * as CryptoJS from 'crypto-js'

function getRandomString(length) {
    const randomChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';
    for ( let i = 0; i < length; i++ ) {
        result += randomChars.charAt(Math.floor(Math.random() * randomChars.length));
    }
    return result;
}
function hexToBase64(str) {
  const stringChange = str.toString()
  return btoa(String.fromCharCode.apply(null, stringChange.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}
async function postTweet() {

  const oauth_consumer_key = '<OAUTH CONSUMER KEY HERE>'
  const oauth_consumer_secret = '<OAUTH CONSUMER SECRET HERE>'
  const oauth_token = '<OAUTH TOKEN HERE>'
  const oauth_token_secret = '<OAUTH TOKEN SECRET HERE>'
  const oauth_signature_method = 'HMAC-SHA1'
  const oauth_version = '1.0'
  const oauth_nonce = getRandomString(42)
  const oauth_timestamp = Math.round(Date.now() / 1000)

  const endpointURL = 'https://api.twitter.com/1.1/statuses/update.json?status';
  
  const tweetData = {
    status: 'I am Tweeting this now'
  }
  
  const encodedTweet = encodeURIComponent(tweetData.status).replace(/!/g, '%21')

  const params = 'POST&' + encodeURIComponent('https://api.twitter.com/1.1/statuses/update.json') + '&include_entities' + encodeURIComponent('=true&') + 'oauth_consumer_key' + encodeURIComponent('='+oauth_consumer_key+'&') + 'oauth_nonce' + encodeURIComponent('='+oauth_nonce+'&') + 'oauth_signature_method' + encodeURIComponent('='+oauth_signature_method+'&') + 'oauth_timestamp' + encodeURIComponent('='+oauth_timestamp+'&') + 'oauth_token' + encodeURIComponent('='+oauth_token+'&') + 'oauth_version' + encodeURIComponent('='+oauth_version+'&') + 'status' + encodeURIComponent('='+encodedTweet)
  const signingKey = encodeURIComponent(oauth_consumer_secret) + '&' + encodeURIComponent(oauth_token_secret)
  //////HMAC-SHA1 Functionality//////
  const hexStr = CryptoJS.HmacSHA1(params, signingKey)
  console.log(hexStr)
  const signature = hexToBase64(hexStr)
  const oauth_signature = encodeURIComponent(signature)
  fetch('https://api.twitter.com/1.1/statuses/update.json', {
    method: 'post',
    headers: {
      'Authorization': 'OAuth oauth_consumer_key="'+oauth_consumer_key+'",oauth_token="'+oauth_token+'",oauth_signature_method="HMAC-SHA1",oauth_timestamp="'+oauth_timestamp+'",oauth_nonce="'+oauth_nonce+'",oauth_version="1.0",oauth_signature="'+oauth_signature+'"',
      'Content-Type': 'application/x-www-form-urlencoded' // 'application/json'
    },
    body: JSON.stringify(tweetData)
  }).then(function(response) {
    return response.json();
  }).then(function(data) {
    console.log('result:', data);
  });
  console.log('postTweet ran')
}

export async function handleRequest(request: Request): Promise<Response> {
  await postTweet()
  return new Response(`Hello worker! this is a ${request.method} request`, {
    headers: { 'content-type': 'text/plain' },
  });
}

But when I run the code with wrangler dev and then do a blank GET request to http://127.0.0.1:8787 with Postman, I get this in my terminal:

<myusername>@<mycomputername> <myappname> % wrangler dev
👂  Listening on http://127.0.0.1:8787
🌀  Detected changes...
💁  Ignoring stale first change
[2022-04-24 15:42:37] GET <myappname>.<myworkername>.workers.dev/ HTTP/1.1 200 OK
{unknown object}
postTweet ran
^C
<myusername>@<mycomputername> <myappname> %

I noticed that the problem probably starts with the fact that const hexStr = CryptoJS.HmacSHA1(params, signingKey) is failing. You can see in the output that console.log(hexStr) is printing {unknown object}

What am I doing wrong? And how can I get my Cloudflare worker to Tweet upon request?

Is there a way to test the code outside of of worker setup? As in, verify that the function is returning the correct values?

Also remember in oAuth the order of the parameters matters too (they must be sorted alphabetically, and values have to be url encoded, so that could be another separate thing that could go wrong: Creating a signature | Docs | Twitter Developer Platform and the other pages there too are relevant