Search code examples
flutterdarttwittertimestampunix-timestamp

Dart/Flutter - How to get second since epoch : Twitter API error code 135 ( Timestamp out of bounds)


Hello I hope you are well. Recently, I have a problem using the Twitter API precisely at the time of publishing a tweet. According to the documentation here, my request must contain in its header a field oauth_timestamp which is the number of seconds since the epoch(seconds since the Unix epoch).

With Dart(Flutter) I retrieve the number of milliseconds elapsed since the epoch which I then divide by 1000 to obtain this value in seconds. Ex:

final oAuthTimestamp =
        Uri.encodeComponent('${DateTime.now().millisecondsSinceEpoch / 1000}');

The values must be encoded in percentage encoding, that's why I put the whole date in the Uri.encodeComponent() method.

But what's weird is that the Twitter API sends me an error telling me that my timestamp is out of bounds, which I can't understand why I get this error.

{"errors":[{"code":135,"message":"Timestamp out of bounds."}]}

Can you help me resolve this error? And tell me the mistakes I made.

Thank you.

Here is below my method to publish a tweet using the Twitter API

Future<void> tweet() async {
    final httpMethod = 'POST';
    final baseURL = 'https://api.twitter.com/1.1/statuses/update.json';
    final status =
        Uri.encodeComponent('Hello this is my first Tweet using Twitter API');
    final includeEntities = Uri.encodeComponent('true');
    final oAuthConsumerKey = Uri.encodeComponent('my_consumer_key');  //hidded for security purpose
    final consumerSecretKey = Uri.encodeComponent('my_consumer_secret_key');  //hidded for security purpose
    final oAuthNonce = Uri.encodeComponent(generateRandomString());  //METHOD TO GENERATE RANDOM STRING ATTACHED BELOW
    final oAuthSignatureMethod = Uri.encodeComponent('HMAC-SHA1');
    final oAuthTimestamp =
        Uri.encodeComponent('${DateTime.now().millisecondsSinceEpoch / 1000}');

    final accessToken = await _twitterAuthService.accessToken; //GETTING MY CURRENT USER ACCESS TOKEN
    final secretToken = await _twitterAuthService.secretToken; //GETTING MY CURRENT USER SECRET TOKEN
    final oAuthSecretToken = Uri.encodeComponent(secretToken);
    final oAuthToken = Uri.encodeComponent(accessToken);

    final oAuthVersion = Uri.encodeComponent('1.0');

    //THIS MY PERCENTAGE ENCODED FIELDS KEY NAME 
    final includeEntitiesField = Uri.encodeComponent('include_entities');
    final oAuthConsumerKeyField = Uri.encodeComponent('oauth_consumer_key');
    final oAuthNonceField = Uri.encodeComponent('oauth_nonce');
    final oAuthSignatureField = Uri.encodeComponent('oauth_signature');
    final oAuthSignatureMethodField =
        Uri.encodeComponent('oauth_signature_method');
    final oAuthTimestampField = Uri.encodeComponent('oauth_timestamp');
    final oAuthTokenField = Uri.encodeComponent('oauth_token');
    final oAuthVersionField = Uri.encodeComponent('oauth_version');
    final statusField = Uri.encodeComponent('status');

    final parameterString = Uri.encodeComponent(
        '$includeEntitiesField=$includeEntities&$oAuthConsumerKeyField=$oAuthConsumerKey&$oAuthNonceField=$oAuthNonce&$oAuthSignatureMethodField=$oAuthSignatureMethod&$oAuthTimestampField=$oAuthTimestamp&$oAuthTokenField=$oAuthToken&$oAuthVersionField=$oAuthVersion&$statusField=$status');
    final encodedBaseUrl = Uri.encodeComponent(baseURL);

    final signatureBaseString = '$httpMethod&$encodedBaseUrl&$parameterString';

    final signingKey = '$consumerSecretKey&$oAuthSecretToken';

    final hmacSCHA1 = Hmac(sha1, convert.utf8.encode(signingKey))
        .convert(convert.utf8.encode(signatureBaseString));

    final oAuthSignature =
        Uri.encodeComponent(convert.base64.encode(hmacSCHA1.bytes));

    final finalUrl = '$baseURL?$statusField=$status';
    final headerString =
        'OAuth $oAuthConsumerKeyField="$oAuthConsumerKey", $oAuthNonceField="$oAuthNonce", $oAuthSignatureField=$oAuthSignature, $oAuthSignatureMethodField=$oAuthSignatureMethod, $oAuthTimestampField=$oAuthTimestamp, $oAuthTokenField=$oAuthToken, $oAuthVersionField=$oAuthVersion';
    
    final response = await _client.post(finalUrl,
        headers: {Fields.authorization.toLowerCase(): headerString});

    print('REQUEST RESPONSE. \nbody = ${response.body}');
  }

here is the method I use to generate an oauth_nonce:

  String generateRandomString() {
    final random = Random();
    final chars =
        'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';

    final generatedString = String.fromCharCodes(
      Iterable.generate(
        42,
        (_) {
          final generatedChars = chars.codeUnitAt(random.nextInt(chars.length));
          return generatedChars;
        },
      ),
    );

    return generatedString;
  }

useful link: Twitter API documentation to POST a Tweet and Create a oauth_signature

P.S: My unsing cypto for hmac-sha1 encryption


Solution

  • I solved the problems that prevented me from publishing a tweet using the Twitter API.

    First of all: I kept my way of retrieving elapsed time (in seconds) since epoch

    final oAuthTimestamp = Uri.encodeComponent('${DateTime.now().millisecondsSinceEpoch / 1000}');
    

    And finally secondly:

    I was getting an error from the Twitter API telling me {"errors":[{"code":32,"message":"Could not authenticate you."}]}

    I simply removed the include_entities field when creating a parameterString

    Before

    final parameterString = Uri.encodeComponent(
            '$includeEntitiesField=$includeEntities&$oAuthConsumerKeyField=$oAuthConsumerKey&$oAuthNonceField=$oAuthNonce&$oAuthSignatureMethodField=$oAuthSignatureMethod&$oAuthTimestampField=$oAuthTimestamp&$oAuthTokenField=$oAuthToken&$oAuthVersionField=$oAuthVersion&$statusField=$status');
    

    After

     final parameterString = Uri.encodeComponent(
            '$oAuthConsumerKeyField=$oAuthConsumerKey&$oAuthNonceField=$oAuthNonce&$oAuthSignatureMethodField=$oAuthSignatureMethod&$oAuthTimestampField=$oAuthTimestamp&$oAuthTokenField=$oAuthToken&$oAuthVersionField=$oAuthVersion&$statusField=$status');
    

    Honestly I don't know why this include_entities field creates an error during authentication. But yet in the documentation, include_entities=true is included in the step of creating a signature (oauth_signature)

    So intuitively I removed this field because I didn't understand its role. And then everything started to work properly😁. Twitter developers should review their documentation and be more explicit about the usefulness of this include_entities field.

    Thanks for everything