0

With some trivial modifications (including a conversion to TypeScript), I copied and pasted the JS code from this Amazon S3 Node.js example into a React Native app that I'm developing with Expo and USB debugging on Android. I want my program to read from a JSON file in one of my Amazon S3 buckets.

import 'react-native-url-polyfill/auto'
import 'react-native-get-random-values'
import { Readable } from 'node:stream'
import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity'
import { fromCognitoIdentityPool } from '@aws-sdk/credential-provider-cognito-identity'
// Create service client module using ES6 syntax.
import {
    S3Client,
    GetObjectCommand,
    GetObjectOutput
} from '@aws-sdk/client-s3'

// Set the AWS Region.
const region = 'us-east-1'
// Create an Amazon S3 service client object.
const client = new S3Client({
    region,
    credentials: fromCognitoIdentityPool({
        client: new CognitoIdentityClient({ region }),
        identityPoolId: 'us-east-1:xxxxxx-xxx-4103-9936-b52exxxxfd6'
    })
})

export const bucketParams = {
    Bucket: 'bucket-name',
    Key: 'filename.json'
}

export const run = async (): Promise<string> => {
    // Create a helper function to convert a ReadableStream to a string.
    const streamToString = (stream: Readable): Promise<string> =>
        new Promise((resolve, reject) => {
            const chunks: Uint8Array[] = []
            stream.on('data', (chunk: Uint8Array): void => {chunks.push(chunk)})
            stream.on('error', reject)
            stream.on('end', (): void => resolve(Buffer.concat(chunks).toString('utf8')))
        })

    // Get the object from the Amazon S3 bucket. It is returned as a ReadableStream.
    const data: GetObjectOutput = await client.send(new GetObjectCommand(bucketParams))
    // Convert the ReadableStream to a string.
    const bodyContents: string = await streamToString(data.Body as Readable)
    return bodyContents
}

I've already confirmed (I think) that the const variable data is successfully assigned a proper value (an object, at least) before its property Body is passed as an argument to streamToString. In light of this, the line const data: GetObjectOutput = await client.send(new GetObjectCommand(bucketParams)) seems to do its job—assign to data a JavaScript object of type GetObjectOutput with a property Body of the union type Readable | ReadableStream<any> | Blob | undefined.

In order to test the initialization of bodyContents, I wrote and called an anonymous function, as follows:

(async () => alert( await run() ))()

When I ran this test, the value of bodyContents was not printed as expected. Instead, I got an error:

[Unhandled promise rejection: TypeError: stream.on is not a function. (In 'stream.on('data', function (chunk) {]
at Contents.tsx:154:16 in Promise$argument_0
at Contents.tsx:152:12 in streamToString
at Contents.tsx:163:57 in run

To see what might be causing this error, I command-clicked, within VS Code, the first call to the function on inside the initialization of streamToString, with the event 'data' as its first argument. I was thereby directed to the corresponding declaration—along with 7 overloads—of on, which is a method inside the class Readable. This class is defined in the file stream.d.ts from the Node core library (node_modules/@types/node/stream.d.ts). Here is the corresponding declaration of on, written in TypeScript:

on(event: 'data', listener: (chunk: any) => void): this;

Considering that my first call to stream.on conforms to this declaration, I don't understand why stream.on is not recognized as a function. I did import the class Readable which contains the method's declaration, after all. To see that the function call conforms to the declaration, note that stream is a Readable object, and I pass in arguments of the forms 'data' (that is, the exact string 'data') and (chunk: any) => void, respectively. Uint8Array falls under any, of course, as does any type.

I realize that the implementations of on may, for all I know, be missing from my project. I've yet to track down a single implementation worthy of the name in my project, despite my diligent command-clicking in VS Code. I'm taking it on faith that I don't have to implement the function myself, since there is no mention of that in the AWS docs. But if I do have to, that means I need to acquaint myself with the algorithm that this overload of on is meant to implement. All I know is that it's supposed to read streams of data.

How do I get this bug out of my hair?

  • 3
    So, React Native uses JavaScriptCore as its JS engine (the JS engine in Safari). This is not nodejs. It does not support nodejs streams and you cannot use AWS code examples that are targeted for nodejs. – jfriend00 Aug 21 '22 at 00:35
  • FYI, I found a note [here](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-up.html) that: *React Native developers should use [AWS Amplify](https://github.com/aws-amplify/amplify-js) to create new projects on AWS. See the [aws-sdk-react-native archive](https://github.com/amazon-archives/aws-sdk-react-native) for details.* – jfriend00 Aug 21 '22 at 00:39
  • @jfriend00 This is helpful info, thanks. I've been taking this approach specifically to avoid using AWS Amplify. I tried Amplify once before, and I encountered another bunch of errors that seemed even less tractable than the one described in the OP. But maybe I really am better off using Amplify. Can I use `expo` or `npm` to add support for Node.js streams to my project, without Amplify? – sophias-fault Aug 21 '22 at 00:59
  • 1
    Nodejs streams are present only within the nodejs run-time which is not in React Native. I have no idea if there's some equivalent NPM library that is independent of nodejs that supports the same API. Somehow I doubt it because there's not much of a market for it. It couldn't work for code that runs in the browser because there's no file access there. And, if you're using Nodejs, you already have it built-in. – jfriend00 Aug 21 '22 at 03:28

0 Answers0