Search code examples
react-nativeamazon-s3aws-sdkaws-sdk-jsaws-sdk-js-v3

TypeError: Cannot read property 'decode' of undefined in React Native with AWS S3 SDK


It's been a joruney trying to get simple file uploads to an AWS S3 bucket working from my react native app built with expo.

Current problem: Upon attempting the upload, I get an error saying: Failed to construct URL with https://[bucket name].s3.us-east-1.amazonaws.com [TypeError: Cannot read property 'decode' of undefined].

I had to install react-native-url-polyfill/auto and import it in my App.js file to get past a previous URL error, as suggested here. I also had to do some funky configurations for babel and Metro due to this issue

My code (with styling and a bunch of other components stripped out to make it a little simpler):

import { View, Modal, Pressable, Text, TextInput, Image, Alert } from 'react-native';
import { useEffect, useContext, useState } from 'react';
import { Button, Icon } from '@rneui/themed';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import * as ImagePicker from 'expo-image-picker';

import { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_REGION, S3_BUCKET } from '@env';

const Memories = () => {
    const [modalVisible, setModalVisible] = useState(false);

    const [permissionStatus, requestPermission] = ImagePicker.useCameraPermissions();

    const client = new S3Client({
        credentials: {
            accessKeyId: AWS_ACCESS_KEY_ID,
            secretAccessKey: AWS_SECRET_ACCESS_KEY,
        },
        region: S3_REGION,
    });

    const pickImage = async () => {
        // No permissions request is necessary for launching the image library
        let result = await ImagePicker.launchImageLibraryAsync({
            mediaTypes: ImagePicker.MediaTypeOptions.Images,
            allowsEditing: true,
            aspect: [4, 3],
            quality: 1,
        });

        console.log(result);

        if (!result.canceled) {
            const awsData = await handleUpload(result.assets[0]).catch((err) => console.log(err));
            console.log(awsData);
        }
    };

    const handleUpload = async (pickerResult) => {
        try {
            const res = await fetch(pickerResult.uri);
            const imgBlob = await res.blob();

            const data = await client.send(
                new PutObjectCommand({
                    ACL: 'public-read',
                    Bucket: S3_BUCKET,
                    Key: `usergen-${Date.now().toString()}`,
                    Body: imgBlob,
                })
            );

            return Promise.resolve(data);
        } catch (err) {
            console.log(err);
            return Promise.reject(err);
        }
    };

    return (
        <View>
            <Modal
                animationType='slide'
                transparent={true}
                visible={modalVisible}
                onRequestClose={() => {
                    setModalVisible(!modalVisible);
                }}>
                <View style={styles.centeredView}>
                    <View style={styles.modalView}>
                        <TextInput
                            multiline
                            placeholder='Body Text'
                            value={bodyText}
                            onChangeText={setBodyText}
                            style={styles.textInput}
                        />
                        <View>
                            <Button style={styles.btnGroup} onPress={pickImage}>
                                Upload from Camera Roll
                            </Button>
                        </View>
                        <Pressable
                            style={[styles.button, styles.buttonClose]}
                            onPress={() => setModalVisible(!modalVisible)}>
                            <Text style={styles.textStyle}>Cancel</Text>
                        </Pressable>
                    </View>
                </View>
            </Modal>
            <Button
                onPress={() => setModalVisible(!modalVisible)}>
                <Icon type='ionicons' name='add' color='white' />
            </Button>
        </View>
    );
};

export default Memories;

As I mentioned, this results in the following console output immediately after completing the image pick process:

Failed to construct URL with https://[bucket name].s3.us-east-1.amazonaws.com [TypeError: Cannot read property 'decode' of undefined]
 LOG  [TypeError: Cannot read property 'decode' of undefined]
 LOG  [TypeError: Cannot read property 'decode' of undefined]
 LOG  undefined

Any help, insights, resources, etc. would be greatly appreciated. This is my first time working with AWS in general. Has not been easy.


Solution

  • Just in case anyone comes along this with the same issue, this ended up being how I got past the problem:

    I followed this solution instead of the babel & Metro configuration changes suggested elsewhere in that Github issue thread. I needed to apply an identical patch to @aws-sdk/middleware-flexible-checksums in addition to the one suggested there.

    The polyfills (react-native-url-polyfill and react-native-get-random-values) were still required and now work without issue.

    I really have no clue what caused this weird chain of errors, n'or do I ever want to think about the 5+ hours I sunk into this issue again! :)