aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.45k stars 2.13k forks source link

Storage.put() throwing [TypeError: undefined is not a function] on React Native #11532

Closed jamesdobry closed 1 year ago

jamesdobry commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

Storage

Amplify Categories

auth, storage, api

Environment information

``` # Put output below this line System: OS: Windows 10 10.0.22621 CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12700K Memory: 7.25 GB / 31.75 GB Binaries: Node: 16.13.1 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.19 - C:\Program Files\nodejs\yarn.CMD npm: 8.1.2 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: 114.0.5735.134 Edge: Spartan (44.22621.1702.0), Chromium (114.0.1823.51) Internet Explorer: 11.0.22621.1 npmPackages: @aws-sdk/smithy-client: ^3.306.0 => 3.306.0 (3.186.0, 3.6.1) @babel/core: ^7.21.8 => 7.21.8 (7.21.3, 7.18.2) @babel/runtime: ^7.21.5 => 7.21.5 (7.21.0) HelloWorld: 0.0.1 aws-amplify: ^5.3.0 => 5.3.0 core-js: ^3.30.2 => 3.30.2 date-fns: ^2.29.3 => 2.29.3 react: 18.2.0 => 18.2.0 (17.0.2) react-native: 0.71.8 => 0.71.8 react-native-reanimated: ~2.14.4 => 2.14.4 reanimated-masonry-list: ^1.0.1 => 1.0.1 ts-jest: ^29.1.0 => 29.1.0 yup: ^1.0.2 => 1.0.2 npmGlobalPackages: @aws-amplify/cli: 12.0.3 @expo/ngrok: 4.1.0 amplify-cli: 1.0.0 aws-cli: 0.0.2 commitizen: 4.3.0 corepack: 0.10.0 create-expo-app: 1.3.2 create-react-app: 5.0.1 eas-cli: 3.13.2 expo-cli: 6.3.2 lerna: 6.6.1 npm: 8.1.2 sharp-cli: 2.1.0 yarn: 1.22.19 ```

Describe the bug

When trying to call Storage.put() with valid details, an error is thrown [TypeError: undefined is not a function].

Expected behavior

The file is uploaded successfully and no error is thrown.

Reproduction steps

  1. Select a file to upload
  2. Call the Storage.put() API
  3. Notice the call fails

Code Snippet

// Put your code below this line.
public async uploadContent(file, details) {
    const fileUri = file.uri;
    const fileName = file.name || fileUri.substring(fileUri.lastIndexOf('/') + 1);
    const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
    const generatedContentName = `${uuid.v4()}.${fileExtension}`;

    const response = await fetch(fileUri);
    const blob = await response.blob();

    await Storage.put(generatedContentName, blob, {
      level: 'protected',
      completeCallback: async () => {
        ...
      },
      errorCallback: (err) => {
        console.error('Unexpected error while uploading', err);
      }
    });
  }

Log output

``` // Put your logs below this line Error uploading file: [TypeError: undefined is not a function] ```

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

Android Resizeable Emulator

Mobile Operating System

Android 13 (API 33)

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

I have hermes turned on.

nadetastic commented 1 year ago

Hi @jamesdobry thank you for opening this issue. Looking at the code snippet you shared, I believe you are trying to use the Storage.put event handlers without setting resumable: true. Can you confirm this?

More information can be seen in the docs as shown here.

jamesdobry commented 1 year ago

Hi @jamesdobry thank you for opening this issue. Looking at the code snippet you shared, I believe you are trying to use the Storage.put event handlers without setting resumable: true. Can you confirm this?

More information can be seen in the docs as shown here.

Hi @nadetastic, I tried setting resumable: true but that doesn't seem to be working either. I could even hardcode the file upload to a text file + basic string and that still throws the same TypeError.

AWSS3UploadTask - Error initializing the upload task [TypeError: undefined is not a function]

nadetastic commented 1 year ago

I see, @jamesdobry could you share you package.json so i can investigate and try to reproduce?

jamesdobry commented 1 year ago

Here is the full package.json

{
  "name": "test-app",
  "version": "0.0.0",
  "private": true,
  "description": "",
  "keywords": [],
  "author": "",
  "homepage": "",
  "license": "UNLICENSED",
  "main": "./index.js",
  "directories": {
    "src": "src"
  },
  "files": [
    "src"
  ],
  "workspaces": {
    "nohoist": [
      "react-native",
      "react-native/**"
    ]
  },
  "scripts": {
    "start": "npx expo start --dev-client",
    "start:tunnel": "npx expo start --tunnel",
    "start:prod": "yarn start --minify",
    "start:prod-full": "yarn start:prod --no-dev",
    "start:android": "yarn start --android",
    "start:ios": "yarn start --ios",
    "start:web": "yarn start --web",
    "start:test": "npx expo run:android",
    "build": "echo \"BUILT\"",
    "test": "jest --passWithNoTests",
    "lint": "eslint ./src --config ../../.eslintrc.json",
    "lint:fix": "yarn lint --fix",
    "format": "prettier --write \"./**/*.{js,jsx,ts,tsx,json}\" --config ../../.prettierrc",
    "copy-schema": "shx cp -r ../api/amplify/backend/api/test-app/build/schema.graphql .",
    "precodegen": "yarn copy-schema",
    "codegen": "amplify codegen",
    "prebuild": "npx expo prebuild --clean",
    "prebuild:staging": "cross-env ENVIRONMENT=staging yarn prebuild",
    "prebuild:production": "cross-env ENVIRONMENT=production yarn prebuild",
    "update:staging": "eas update --branch staging",
    "update:production": "eas update --branch production",
    "predeploy:staging": "yarn prebuild:staging",
    "deploy:staging": "eas build --profile staging --platform all",
    "predeploy:production": "yarn prebuild:production",
    "deploy:production": "eas build --profile production --platform all",
    "deploy-submit": "yarn deploy:production --auto-submit",
    "submit:production": "eas submit --profile production --platform all --latest",
    "android": "npx expo run:android",
    "ios": "npx expo run:ios"
  },
  "dependencies": {
    "@amplitude/analytics-react-native": "^1.1.3",
    "@aws-sdk/smithy-client": "^3.306.0",
    "@azure/core-asynciterator-polyfill": "^1.0.2",
    "@config-plugins/detox": "^5.0.1",
    "@dank-style/animation-plugin": "latest",
    "@dank-style/react": "latest",
    "@expo/vector-icons": "^13.0.0",
    "@react-native-async-storage/async-storage": "1.17.11",
    "@react-native-community/blur": "^4.3.0",
    "@react-native-community/datetimepicker": "6.7.3",
    "@react-native-community/netinfo": "9.3.7",
    "@react-native-firebase/analytics": "^17.4.2",
    "@react-native-firebase/app": "^17.4.2",
    "@react-navigation/native": "^6.1.6",
    "@react-navigation/native-stack": "^6.9.12",
    "@sentry/react-native": "4.15.2",
    "amazon-cognito-identity-js": "^6.3.0",
    "aws-amplify": "^5.3.0",
    "core-js": "^3.30.2",
    "date-fns": "^2.29.3",
    "detox": "^20.7.2",
    "expo": "~48.0.18",
    "expo-application": "~5.1.1",
    "expo-blur": "~12.2.2",
    "expo-build-properties": "~0.6.0",
    "expo-constants": "~14.2.1",
    "expo-dev-client": "~2.2.1",
    "expo-device": "~5.2.1",
    "expo-font": "^11.1.1",
    "expo-image-picker": "~14.1.1",
    "expo-linear-gradient": "~12.1.2",
    "expo-localization": "~14.1.1",
    "expo-notifications": "~0.18.1",
    "expo-splash-screen": "~0.18.2",
    "expo-status-bar": "~1.4.4",
    "expo-system-ui": "~2.2.1",
    "expo-tracking-transparency": "~3.0.3",
    "expo-updates": "~0.16.4",
    "formik": "^2.2.9",
    "i18n-js": "^4.2.3",
    "native-base": "^3.4.28",
    "promise.allsettled": "^1.0.6",
    "react": "18.2.0",
    "react-native": "0.71.8",
    "react-native-gesture-handler": "~2.9.0",
    "react-native-get-random-values": "~1.8.0",
    "react-native-paper": "^5.5.1",
    "react-native-reanimated": "~2.14.4",
    "react-native-recoil-persist": "^0.0.6",
    "react-native-safe-area-context": "4.5.0",
    "react-native-screens": "~3.20.0",
    "react-native-svg": "13.4.0",
    "react-native-url-polyfill": "^1.3.0",
    "react-native-uuid": "^2.0.1",
    "react-native-vector-icons": "^9.2.0",
    "reanimated-masonry-list": "^1.0.1",
    "recoil": "^0.7.7",
    "recoil-sync": "^0.2.0",
    "sentry-expo": "~6.2.0",
    "yup": "^1.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.21.8",
    "@babel/runtime": "^7.21.5",
    "@config-plugins/detox": "^5.0.1",
    "@tsconfig/react-native": "^2.0.3",
    "@types/jest": "^29.5.1",
    "@types/react": "^18.0.28",
    "@types/react-test-renderer": "^18.0.0",
    "babel-jest": "^29.5.0",
    "cross-env": "^7.0.3",
    "detox": "^20.7.2",
    "jest": "^29.5.0",
    "jest-circus": "^29.5.0",
    "jest-expo": "^48.0.2",
    "react-native-web": "^0.18.12",
    "sharp-cli": "^4.1.1",
    "shx": "^0.3.4",
    "ts-jest": "^29.1.0"
  },
  "jest": {
    "preset": "jest-expo",
    "transformIgnorePatterns": [
      "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|(@sentry/.*)|sentry-expo|native-base|react-native-svg)"
    ],
    "collectCoverage": false,
    "collectCoverageFrom": [
      "**/*.{js,jsx,ts,tsx}",
      "!**/coverage/**",
      "!**/node_modules/**",
      "!**/babel.config.js",
      "!**/jest.setup.js"
    ]
  }
}
jamesdobry commented 1 year ago

I have a slight update. I get this error on Mac (when running on iOS iPhone 14 Pro simulator) instead of running on Windows and Android where I got the initial error:

Error uploading file: [TypeError: _useFipsEndpoint is not a function. (In '_useFipsEndpoint()', '_useFipsEndpoint' is undefined)]

nadetastic commented 1 year ago

@jamesdobry thanks for providing the additional info, Im not able to reproduce this error and im able to call Storage.put successfully. However i'm curious how you're code is configured - specifically, it looks like you may have your uploadContent() method within a Class. Can you clarify how you have this defined? And if you do have it within a class, can you try calling Storage.put directly, and also try wrapping it in a try/catch block?

async function uploadContent(file,details) {
    ...
    try {
        const res = await Storage.put(generatedContentName, blob, {
            level: 'protected',
            completeCallback: async () => {
                ...
            },
            errorCallback: (err) => {
                console.error('Unexpected error while uploading', err);
            }
        });
    } catch (err) {
        console.log(err)
    }
    ...
}
jamesdobry commented 1 year ago
try {
      const res = await Storage.put(generatedContentName, blob, {
              level: 'protected',
              completeCallback: async () => {
              ...
          },
              errorCallback: (err) => {
              console.error('Unexpected error while uploading', err);
              }
      });
  } catch (err) {
      console.log(err)
  }

Hi, so this is being executed in a callback in a React form component. I had moved it out into a Service class function as to clean up the business logic that was going on in the React component. By putting the call directly back into the useCallback, it still fails with the same error.

Not sure if this helps with debugging but the errorCallback never fires, but the try-catch is what is catching the error.

nadetastic commented 1 year ago

Interesting @jamesdobry - could you try it as described in the documentation here?

In short, add a property for resumable:true and do not await the call for Storage.put() or wrap it in a try/catch. I'd like to see if the errorCallback will be invoked.

const upload = Storage.put(file.name, file, {
        resumable: true,
        completeCallback: (event) => {
            console.log(`Successfully uploaded ${event.key}`);
        },
        progressCallback: (progress) => {
            console.log(`Uploaded: ${progress.loaded}/${progress.total}`);
        },
        errorCallback: (err) => {
            console.error('Unexpected error while uploading', err);
        }
})
jamesdobry commented 1 year ago

Interesting @jamesdobry - could you try it as described in the documentation here?

In short, add a property for resumable:true and do not await the call for Storage.put() or wrap it in a try/catch. I'd like to see if the errorCallback will be invoked.

const upload = Storage.put(file.name, file, {
        resumable: true,
        completeCallback: (event) => {
            console.log(`Successfully uploaded ${event.key}`);
        },
        progressCallback: (progress) => {
            console.log(`Uploaded: ${progress.loaded}/${progress.total}`);
        },
        errorCallback: (err) => {
            console.error('Unexpected error while uploading', err);
        }
})

I did the following:

const fileUri = file.uri;
    const fileName = file.name || fileUri.substring(fileUri.lastIndexOf('/') + 1);
    const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
    const generatedContentName = `test.${fileExtension}`;

    const response = await fetch(fileUri);
    const blob = await response.blob();

        const res = Storage.put(generatedContentName, blob, {
          resumable: true,
            completeCallback: (event) => {
              console.log(`Successfully uploaded ${event.key}`);
            },
            progressCallback: (progress) => {
              console.log(`Uploaded: ${progress.loaded}/${progress.total}`);
            },
            errorCallback: (err) => {
              console.error('Unexpected error while uploading', err);
            }
          });

          console.log('res', res);
      } catch (err) {
        console.error('fail', err)
      }

and got:

 LOG  res {"bytesUploaded": 0, "completedParts": [], "emitter": {"_events": {"error": [Array], "uploadComplete": [Function anonymous], "uploadPartProgress": [Function anonymous]}, "_eventsCount": 3, "_maxListeners": undefined}, "file": {"_data": {"__collector": [Object], "blobId": "E5C566EA-5041-4170-B1D6-E1A2AD37CAF2", "name": "A5A0043C-DD20-45AE-ADA6-DD7D9FF9BF38.jpg", "offset": 0, "size": 3566999, "type": "image/jpeg"}}, "fileId": "3566999-image/jpeg-app-staging--test.jpg", "inProgress": [], "params": {"Body": {"_data": [Object]}, "Bucket": "app-staging", "ContentType": "binary/octet-stream", "Key": "test.jpg"}, "partSize": 5242880, "prefixPromise": {"_h": 0, "_i": 0, "_j": null, "_k": null}, "queueSize": 4, "queued": [], "s3client": {"config": {"apiVersion": "2006-03-01", "base64Decoder": [Function fromBase64], "base64Encoder": [Function toBase64], "bodyLengthChecker": [Function calculateBodyLength], "bucketEndpoint": false, "credentialDefaultProvider": [Function credentialDefaultProvider], "credentials": [Function anonymous], "customUserAgent": [Array], "defaultUserAgentProvider": [Function anonymous], "disableHostPrefix": false, "endpoint": [Function anonymous], "eventStreamMarshaller": [EventStreamMarshaller], "eventStreamSerdeProvider": [Function eventStreamSerdeProvider], "forcePathStyle": false, "isCustomEndpoint": false, "logger": [Object], "maxAttempts": [Function anonymous], "md5": [Function Md5], "region": [Function region], "regionInfoProvider": [Function defaultRegionInfoProvider], "requestHandler": [AxiosHttpHandler], "retryStrategy": [Function retryStrategy], "runtime": "react-native", "serviceId": "S3", "sha256": [Function Sha256], "signer": [Function signer], "signingEscapePath": false, "streamCollector": [Function streamCollector], "streamHasher": [Function blobHasher], "systemClockOffset": 0, "tls": true, "urlParser": [Function parseUrl], "useAccelerateEndpoint": false, "useArnRegion": [Function anonymous], "useDualstackEndpoint": [Function anonymous], "useFipsEndpoint": [Function useFipsEndpoint], "utf8Decoder": [Function fromUtf8], "utf8Encoder": [Function toUtf8]}, "middlewareStack": {"add": [Function add], "addRelativeTo": [Function addRelativeTo], "applyToStack": [Function cloneTo], "clone": [Function clone], "concat": [Function concat], "identify": [Function identify], "remove": [Function remove], "removeByTag": [Function removeByTag], "resolve": [Function resolve], "use": [Function use]}}, "state": 1, "storage": [Function MemoryStorage], "storageSync": {"_h": 0, "_i": 1, "_j": undefined, "_k": null}, "totalBytes": 3566999}
 ERROR  There was a problem sending log messages to your development environment [PrettyFormatPluginError: undefined is not a function]
 ERROR  [ERROR] 38:23.815 AWSS3UploadTask - Error initializing the upload task [TypeError: undefined is not a function]
 ERROR  Unexpected error while uploading [TypeError: undefined is not a function]
nadetastic commented 1 year ago

@jamesdobry After a bit of testing, I am now getting an error that's not quite the same as you are seeing. In short, it looks like the file size that is uploaded to S3 vs the one selected on the client/device are different.

There was a problem sending log messages to your development environment [PrettyFormatPluginError: undefined is not a function]
 LOG  Uploaded: 1294950/1294950
 ERROR  [ERROR] 47:08.83 AWSS3UploadTask - error completing upload [Error: File size does not match between local file and file on s3]
 LOG  [Error: File size does not match between local file and file on s3]

However in either case, the file that is uploaded is much smaller than the file that was selected on the device.

This particular issue seems to be specific to version of aws-amplify greater than 5.2.7. Since it appears you are on 5.3.0 could you try using version 5.2.7 to see if the error you are experience is related to what I am seeing?


nadetastic commented 1 year ago

Hi @jamesdobry following up here - the issue that was identified on #11549 has be resolved with the latest version of aws-amplify 5.3.3 could you try using this latest version to try an identify if this was a related issue?

Thanks!

jamesdobry commented 1 year ago

@nadetastic I upgraded to 5.3.3 in the app project. That still did not work. When I did yarn list --pattern aws-amplify I noticed that I had some older version 4 references in the other projects.

Since this is a monorepo I wondered if yarn workspaces was being confused. I updated those projects to also have 5.3.3.

I also notice references to @aws-amplify. Do I need these? I think I was flip-flopping between the two projects during initial setup. The only reference was to @aws-amplify/api when I pulled a type reference from it into my app code. I removed that reference.

From the update of the 5.3.3 throughout as well as the remove for the @aws-amplify reference, I was able to successfully upload an image file.

EDIT: I would like to add that the initial function I wrote above does work successfully. I do not need to use the resumable: true way of upload and can do it in the asynchronous way.

nadetastic commented 1 year ago

Could you share a sample of how your mono repo is setup so i can try and reproduce? @jamesdobry

nadetastic commented 1 year ago

Hi @jamesdobry wanted to follow up here - let me know if you were able to get this resolved.

nadetastic commented 1 year ago

Hi @jamesdobry im going to close this issue out as there hasn't been a response in a while. Let me know if you still have questions and we can continue discussing. Thanks.