Git Product home page Git Product logo

edeckers / react-native-blob-courier Goto Github PK

View Code? Open in Web Editor NEW
132.0 3.0 10.0 3.34 MB

Use this library to efficiently download and upload blobs in React Native.

License: Mozilla Public License 2.0

Kotlin 45.77% JavaScript 0.57% Java 2.81% TypeScript 22.40% C 0.04% Objective-C 0.72% Swift 21.41% Ruby 1.87% Makefile 0.22% Shell 3.77% Objective-C++ 0.43%
react-native blob transfer files download upload courier android upload-blobs ios

react-native-blob-courier's Introduction

react-native-blob-courier

License: MPL 2.0 Build

Use this library to efficiently download and upload data in React Native. The library was inspired by rn-fetch-blob, and is focused strictly on http file transfers.

Installation

Install using yarn

yarn add react-native-blob-courier

Or install using npm

npm install react-native-blob-courier

Requirements

  • Android >= 24
  • Android Gradle Plugin >= 7.5.1
  • iOS >= 13.0
  • JDK >= 11
  • React Native >= 0.69.x

Note: you may have success with earlier versions of React Native but these are neither tested nor supported.

Usage

The library provides both a fluent and a more concise interface. In the examples the concise approach is applied; fluent interface is demonstrated later in this document.

Straightforward down- and upload

import BlobCourier from 'react-native-blob-courier';

// ...

// Download a file
const request0 = {
  filename: '5MB.zip',
  method: 'GET',
  mimeType: 'application/zip',
  url: 'http://ipv4.download.thinkbroadband.com/5MB.zip',
};

const fetchedResult = await BlobCourier.fetchBlob(request0);
console.log(fetchedResult);
// {
//   "data": {
//     "absoluteFilePath": "/path/to/app/cache/5MB.zip",
//     "response": {
//       "code":200,
//       "headers": {
//         "some_header": "some_value",
//         ...
//       }
//     },
//   },
//   "type":"Unmanaged"
// }

// ...

// Upload a file
const absoluteFilePath = fetchedResult.data.absoluteFilePath;

const request1 = {
  absoluteFilePath,
  method: 'POST',
  mimeType: 'application/zip',
  url: 'https://file.io',
};

const uploadResult = await BlobCourier.uploadBlob(request1);

console.log(uploadResult):
// {
//   "response": {
//     "code": {
//     "data": "<some response>",
//     "headers": {
//       "some_header": "some_value",
//        ...
//      }
//   }
// }

// Multipart file upload
const absoluteFilePath = fetchedResult.data.absoluteFilePath;

const request2 = {
  method: 'POST',
  parts: {
    body: {
      payload: 'some_value',
      type: 'string',
    },
    file: {
      payload: {
        absoluteFilePath,
        mimeType: 'application/zip',
      },
      type: 'file',
    },
  },
  url: 'https://file.io',
};

const multipartUploadResult = await BlobCourier.uploadBlob(request1);

console.log(multipartUploadResult):
// {
//   "response": {
//     "code": {
//     "data": "<some response>",
//     "headers": {
//       "some_header": "some_value",
//        ...
//      }
//   }
// }

Transfer progress reporting

import BlobCourier from 'react-native-blob-courier';

// ...

// Download a file
const request0 = {
  // ...
  onProgress: ((e: BlobProgressEvent) => {
    console.log(e)
    // {
    //  "written": <some_number_of_bytes_written>,
    //  "total": <some_total_number_of_bytes>
    // }
  })
};

const fetchedResult = await BlobCourier.fetchBlob(request0);

// ...

// Upload a file
const request1 = {
  // ...
  onProgress: ((e: BlobProgressEvent) => {
    console.log(e)
    // {
    //  "written": <some_number_of_bytes_written>,
    //  "total": <some_total_number_of_bytes>
    // }
  })
};

const uploadResult = await BlobCourier.uploadBlob(request1)

// ...

// Set progress updater interval
const request2 = ...

const someResult =
  await BlobCourier
    .fetchBlob({
      ...request2,
      progressIntervalMilliseconds: 1000,
    });

Managed download on Android (not available on iOS)

import BlobCourier from 'react-native-blob-courier';

// ...

const request = {
  android: {
    useDownloadManager: true // <--- set useDownloadManager to "true"
  },
  filename: '5MB.zip',
  method: 'GET',
  mimeType: 'application/zip',
  url: 'http://ipv4.download.thinkbroadband.com/5MB.zip',
};

const fetchResult = await BlobCourier.fetchBlob(request);

console.log(fetchedResult);
// {
//   "data": {
//     "result": "SUCCESS",
//     "absoluteFilePath": "/path/to/app/cache/5MB.zip"
//   },
//   "type":"Managed"
// }

Multipart upload

Sometimes order of multipart fields matters, and Blob Courier respects the order in which parts are provided. There is a catch though: due to how JavaScript works, when object keys are regular strings they are kept in the order they were added unless the keys are strings containing numbers, e.g.:

Object.keys({
  "b": "some_value1",
  "c": "some_value2",
  "a": "some_value3",
})

// ['b', 'c', 'a']

Object.keys({
  "b": "some_value1",
  "c": "some_value2",
  "a": "some_value3",
  "3": "some_value4",
  "2": "some_value5",
  "1": "some_value6",
});

// ['1', '2', '3', 'b', 'c', 'a']

The way to work around this, is to wrap all keys in a Symbol, by using Symbol.for. Do not use Symbol(<value>), this will not work, e.g.:

Object.getOwnPropertySymbols({
  [Symbol.for("b")]: "some_value1",
  [Symbol.for("c")]: "some_value2",
  [Symbol.for("a")]: "some_value3",
  [Symbol.for("3")]: "some_value4",
  [Symbol.for("2")]: "some_value5",
  [Symbol.for("1")]: "some_value6",
});

// [Symbol('b'), Symbol('c'), Symbol('a'), Symbol('3'), Symbol('2'), Symbol('1')]

Cancel request

import BlobCourier from 'react-native-blob-courier';

// ...

const abortController = new AbortController();

const { signal } = abortController;

const request0 = {
  // ...
  signal,
};

try {
  BlobCourier.fetchBlob(request0);

  abortController.abort();
} catch (e) {
  if (e.code === ERROR_CANCELED_EXCEPTION) {
    // ...
  }
}

// ...

Fluent interface

Blob Courier provides a fluent interface, that both protects you from using impossible setting combinations and arguably improves readability.

const req0 = ...

const someResult =
  await BlobCourier
    .settings({
      progressIntervalMilliseconds: 1000,
    })
    .onProgress((e: BlobProgressEvent) => {
      // ...
    })
    .useDownloadManagerOnAndroid({
      description: "Some file description",
      enableNotification: true,
      title: "Some title"
    })
    .fetchBlob(req0)

Available methods

fetchBlob(input: BlobFetchRequest)

Required

Field Type Description
filename string The name the file will have on disk after fetch.
mimeType string What is the mime type of the blob being transferred?
url string From which url will the blob be fetched?

Optional

Field Type Description Default
android AndroidSettings Settings to be used on Android { downloadManager: {}, target: 'cache', useDownloadManager: false }
ios IOSSettings Settings to be used on iOS { target: 'cache' }
headers { [key: string]: string } Map of headers to send with the request {}
method string Representing the HTTP method GET
onProgress (e: BlobProgressEvent) => void Function handling progress updates () => { }
signal AbortSignal Request cancellation manager null

Response

Field Type Description
type "Managed" | "Unmanaged" Was the blob downloaded through Android Download Manager, or without?
data BlobManagedData | BlobUnmanagedData Either managed or HTTP response data

uploadBlob(input: BlobUploadRequest)

Alias for:

const someResult =
  await BlobCourier
   // ...
   .uploadParts({
     headers,
     method,
     parts: {
       file:
         payload: {
           absoluteFilePath,
           filename,
           mimeType,
         },
         type: 'file',
       },
     },
     returnResponse,
     url,
   })

Required

Field Type Description
absoluteFilePath string Path to the file to be uploaded
mimeType string Mime type of the blob being transferred
url string Url to upload the blob to

Optional

Field Type Description Default
filename string Name of the file on disk <name part of 'absoluteFilePath'>
headers { [key: string]: string } Map of headers to send with the request {}
method string The HTTP method to be used in the request "POST"
multipartName string Name for the file multipart "file"
onProgress (e: BlobProgressEvent) => void Function handling progress updates () => { }
returnResponse boolean Return the HTTP response body? false
signal AbortSignal Request cancellation manager null

uploadParts(input: BlobMultipartUploadRequest)

Required

Field Type Description
parts { [key: string]: BlobMultipart } The parts to be sent
url string Url to upload the blob to

Optional

Field Type Description Default
headers { [key: string]: string } Map of headers to send with the request {}
method string The HTTP method to be used in the request "POST"
onProgress (e: BlobProgressEvent) => void Function handling progress updates () => { }
returnResponse boolean Return the HTTP response body? false
signal AbortSignal Request cancellation manager null

Response

Field Type Description
response BlobUnmanagedHttpResponse The HTTP response

AndroidDownloadManagerSettings

Field Type Description
description? string Description of the downloaded file
enableNotification? boolean Display notification when download completes
title? string Title to be displayed with the download

AndroidSettings

Field Type Description
downloadManager AndroidDownloadManagerSettings Settings to be used on download manager
target "cache" | "data" Where will the file be stored?
useDownloadManager boolean Enable download manager on Android?

BlobManagedData

Field Type Description
absoluteFilePath string The absolute file path to where the file was stored
result "SUCCESS" | "FAILURE" Was the request successful or did it fail?

BlobMultipart

Required

Field Type Description
payload BlobMultipartFormData | BlobMultipartFormDataFile Contains the payload of the part
type "string" | "file" What is the type of the payload?

BlobMultipartFormData

Type of string | { [key:string] : any }

BlobMultipartFormDataFile

Required

Field Type Description
absoluteFilePath string Path to the file to be uploaded
mimeType string Mime type of the blob being transferred

Optional

Field Type Description Default
filename string Name of the file on disk <name part of 'absoluteFilePath'>

BlobProgressEvent

Field Type Description
written number Number of bytes processed
total number Total number of bytes to be processed

BlobUnmanagedData

Field Type Description
absoluteFilePath string The absolute file path to where the file was stored
response BlobUnmanagedHttpResponse HTTP response, including headers and status code

BlobUnmanagedHttpResponse

Field Type Description
code number HTTP status code
headers { [key: string]: string } HTTP response headers

IOSSettings

Field Type Description
target "cache" | "data" Where will the file be stored?

Example app

You can find an example of how to use the library in the example directory.

Android

Permissions

Android 5.1 and below (API level < 23)

Add the following line to AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" (...)>

+   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+   <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
    (...)
    <application (...)>
      <activity (...)>
        <intent-filter>
          (...)
+          <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
        </intent-filter>
    (...)

Android 6.0+ (API level 23+)

Grant permissions using the PermissionAndroid API, like so:

const function App = () => {

  // ...

  React.useEffect(() => {
    const requestPermissionAsync = async () => {
      try {
        await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
        );

        // ...
      } catch (err) {
        console.error(err);
      }

      // ...
    };

    requestPermissionAsync();
  }, []);

  // ...

iOS

Add to Info.plist of your app:

<key>NSAllowsArbitraryLoads</key>
<true/>

Using the integrated download manager for Android

This library allows you to use the integrated download manager on Android, this option is not available for iOS.

To enable the download manager, simply set the request's useDownloadManager property of field android to true when passing it to fetchBlob, or call the useDownloadManagerOnAndroid method when using the fluent interface.

Shared directories

As this library is focused on transferring files, it only supports storage to the app's cache and data directories. To move files from these app specific directories to other locations on the filesystem, use another library like @react-native-community/cameraroll, e.g.:

import BlobCourier from 'react-native-blob-courier';
import CameraRoll from '@react-native-community/cameraroll';

// ...

const request = {
  filename: 'teh_cage640x360.png',
  method: 'GET',
  mimeType: 'image/png',
  url: 'https://www.placecage.com/640/360',
};

const cageResult = await BlobCourier.fetchBlob(request)

const cageLocalPath = cageResult.data.absoluteFilePath

CameraRoll.save(cageLocalPath);

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

Code of Conduct

Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

License

MPL-2.0

react-native-blob-courier's People

Contributors

andarius avatar dependabot[bot] avatar edeckers avatar github-actions[bot] avatar gladiuscode avatar majirosstefan avatar markholland avatar semantic-release-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

react-native-blob-courier's Issues

Feature: Intercept downloads to transcode/decrypt payload

Currently I am downloading encrypted payload and its stored on storage as a file. To display the data in the app
After the download I have to read the whole file that was just written to storage back into memory (in chunks or as a whole), decrypt it, save it back to storage and removing the encrypted version of the file.

I wonder now if the library could allow the user to supply a file writing interceptor that this lib delegates the file writing to like this:

await BlobCourier
  .onDownload({
    blockSize: 16384 * 5,
    transcode: (input: ArrayBuffer, last: boolean):Promise<ArrayBuffer> {
      last ? return aes.finish(input) : aes.decrypt(input)
    }
  }
  ).fetch("https://example.org/encrypted.enc")

Some starting point maybe?
https://medium.com/swlh/okhttp-interceptors-with-retrofit-2dcc322cc3f3

It would be crucial to operate on streams rather than having the whole response in memory.

Add target settings

Allow configuration of a download target, for now: data and cache, with a fallback to cache.

  • Implement TypeScript
  • Implement Android
  • Implement iOS
  • Adapt existing testsing Android
  • Add Android tests
  • Adapt TypesScript tests
  • Add TypeScript tests
  • Add iOS tests
  • Adapt documentation

Convert the example app to something interesting

- [ ] Use test files that are hosted in this repository, so the app won't break when links out of our control become invalid
note: removed this requirement, because it would make the example unnecessarily compilicated; will find a fix for this for testing though.

  • Add a 'download' button
  • Don't automatically start download and upload on loading the app
  • Add an 'upload' button, or change the 'download' button to an 'upload' button when download completed
  • Display progress
  • Allow switch between managed and unmanaged download on Android, display this option on iOS, but disable it
  • Only check permissions on Android, it is not supported on iOS

Update to version 1.0.5 ios Bug

Update to version 1.0.5 ios or the body of your POST request is not well-formed multipart/form-data error android without any problems.

Add settings

  • progress updater interval to avoid JS bridge congestion
  • enable or disable upload response retrieval

Extend multipart upload feature with support for multipart/related requests

Some background: #135

Currently react-native-blob-courier supports multipart/form-data file uploads only, it should support multipart/related as well.

Proposed solution

  • Add a multipartType: "form-data" | "related" field to BlobMultipartMapUploadRequest
  • Add a Content-ID field to each part of the request. The value of the Content-ID-field will be the name of the key of the part in the parts dictionary of the upload request, so contents of separate parts can refer to each other through cid's.
  • Add inline: boolean = false field to BlobMultipartFormData

Progress

  • Add multipartType to BlobMultipartMapUploadRequest
  • Add inline field to BlobMultiPartFormData
  • Add Content-ID field to each part of the request in Android code base
  • Add Content-ID field to each part of the request in iOS code base
  • Process inline field in Android code base
  • Process inline field in iOS code base
  • Add TypeScript tests
  • Add Android tests
  • Add iOS tests

Release version 0.9.3

  • Fix message congestion / bridge chattiness due to progress updates
  • Fix Android performance by running IO on thread

Improve Android code quality

  • Ensure it offers the exact same functionality the iOS code base
  • Separate download parameter validation (#82)
  • Split managed unmanaged download (#81)
  • Untangle parameter verification (#80)
  • Separation of concerns (#78, #79)
  • Simplify threaded tests (#104)
  • Reduce code duplication in tests (#104)
  • Clean cut between native and React domain (#98)
  • More specific error codes (#103)

Release version 1.0.0

Version 1.0.0 of the release will contain (Android and iOS, unless stated differently):

  • Subscribing to download progress
  • Subscribing to upload progress

Allow to cancel downloads

Is your feature request related to a problem? Please describe.
Allow canceling a download in progress

Describe the solution you'd like
Something like AbortController from fetch.

Warning: Module BlobCourierEventEmitter requires main queue setup ...

Describe the bug

Thank you for this great library since the other blob managers are not maintained. Since I installed this packaged I am getting the yellow box warning below. Nothing that stops my work but was wondering if it is something that needs to be fixed

To Reproduce
Steps to reproduce the behavior:

  1. Install react-native-blob-courier according to instructions
  2. Run RN Project

Expected behavior
Not have yellow box warning

Screenshots
image

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

Add some useful tests

  • Add native end-to-end tests for fetch and upload for Android
  • Add native end-to-end tests for fetch and upload for iOS

- [ ] Add some interface tests for fetch and upload to TypeScript code base replaced by #45
- [ ] Use test files that are hosted in this repository, so the app won't break when links out of our control become invalid Not super relevant, improve it when it is needed.

is add body?

const request = {
    absoluteFilePath: image.sourceURL,
    method: 'POST',
    mimeType: image.mime,
    url: OSS.host,
    returnResponse: true,
    headers: {
    'Content-Type': 'multipart/form-data',
    },
    body: {
      policy: OSS.policy,
      OSSAccessKeyId: OSS.accessid,
      signature: OSS.signature,
      callback: OSS.callback,
      'x:token ': token,
      key: OSS.dir + imageName,
      success_action_status: 200,
      file: image.sourceURL,
    },
};

Handle more unhappy flows

Android

Reject

  • Server unavailable
  • File unavailable
  • Network unavailable (fixed with instrumented test, which is not run in ci due to emulator complications)

- [ ] Not enough space left on device can't find a reasonable manner to do this

Resolve

  • URL unavailable (ie. non-ok http status code)

iOS

Reject

  • Server unavailable
  • File unavailable

- [ ] Not enough space left on device can't find a reasonable manner to do this
- [ ] Network unavailable can't find a reasonable manner to do this

Resolve

  • URL unavailable (ie. non-ok http status code)

Improve documentation

  • Add documentation for progress
  • Improve installation instructions
  • Add actions badge
  • Add example explaining how to move downloaded files to media library
  • Improve overall readability
  • Explain settings feature
  • Update interfaces and type names
  • Update responses

Improve iOS code quality

  • Use linter
  • Ensure it offers the exact same functionality the Android code base does
  • Separate download parameter validation
  • Untangle parameter verification
  • Separation of concerns
  • Simplify threaded tests
  • Make threaded test run no longer than required, instead of the currently fixed 10 seconds
  • Reduce code duplication in tests
  • Clean cut between native and React domain
  • More specific error codes

Selection of file outside storage scope causes crash

Describe the bug
Originally reported in #66.

When providing an out of scope absolutePath to uploadBlob, the library causes a crash on Android 10+, due to the introduction of Scoped storage

To Reproduce
Steps to reproduce the behavior:

  1. Create an empty ReactNative app
  2. Install react-native-image-crop-picker
  3. Select a file with the crop picker
  4. Provide the path from the file to BlobCourier.uploadBlob

Expected behavior
BlobCourier will upload a file regardless of whether it is in scope of the app or not.

Desktop (please complete the following information):

  • OS: Android
  • Version: 10+

Additional context
Example stack trace

2020-12-11 19:33:16.012 1042-3167/com.blobtest.app E/AndroidRuntime: FATAL EXCEPTION: Thread-16
    Process: com.kelapp, PID: 1042
    java.io.FileNotFoundException: file:/storage/emulated/0/Android/data/com.kelapp/files/Pictures/d2f82461-1f50-4b46-b4f5-61ae2b8d2d5a.jpg: open failed: ENOENT (No such file or directory)
        at libcore.io.IoBridge.open(IoBridge.java:496)
        at java.io.FileInputStream.<init>(FileInputStream.java:159)
        at okio.Okio.source(Okio.java:168)
        at okhttp3.RequestBody$3.writeTo(RequestBody.java:119)
        at okhttp3.MultipartBody.writeOrCountBytes(MultipartBody.java:173)
        at okhttp3.MultipartBody.writeTo(MultipartBody.java:114)
        at io.deckers.blob_courier.BlobCourierProgressRequest.writeTo(BlobCourierProgressRequest.kt:33)
        at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:72)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:254)
        at okhttp3.RealCall.execute(RealCall.java:92)
        at io.deckers.blob_courier.BlobCourierModuleKt$startBlobUpload$1.invoke(BlobCourierModule.kt:156)
        at io.deckers.blob_courier.BlobCourierModuleKt$startBlobUpload$1.invoke(Unknown Source:0)
        at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
    	Suppressed: java.net.ProtocolException: unexpected end of stream
        at okhttp3.internal.http1.Http1Codec$FixedLengthSink.close(Http1Codec.java:307)
        at okio.ForwardingSink.close(ForwardingSink.java:47)
        at okio.RealBufferedSink.close(RealBufferedSink.java:248)
        at okio.ForwardingSink.close(ForwardingSink.java:47)
        at okio.RealBufferedSink.close(RealBufferedSink.java:248)
        at kotlin.io.CloseableKt.closeFinally(Closeable.kt:60)
        		... 19 more
     Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7737)
        at libcore.io.IoBridge.open(IoBridge.java:482)
        	... 24 more

Add support for multipart uploads

Initially proposed in #62

The current implementation lacks support for multipart uploads. Therefore it is impossible to send a body with an upload request.

Proposed solution
Add a body field to the upload message, which allows the developer to specify a body to be sent with the request. When a body is provided, the upload is automatically converted to multipart

Progress

  • Determine message interface (see below)
  • Add message interface to TypeScript
  • Make uploadBlob an alias to uploadParts in TypeScript
  • Implement Android
  • Implement iOS
  • Adapt tests Android
  • Adapt tests iOS
  • Add tests iOS
  • Add tests TypeScript
  • Refactor part to contain payload field
  • Clean-up and simplify code
  • Update the docs

Suggested message interface

const request = {
  method: 'POST',
  parts: {
    body: {
      payload: 'some_value',
      type: 'string',
    },
    json: {
      payload: {
        some_key: 'some_value',
      },
      type: 'string',
    },
    file: {
      payload: {
        absoluteFilePath,
        mimeType: 'application/zip',
      },
      type: 'file',
    },
    (...)
    <part_n_key>: <part_n>,
  },
  url: 'https://file.io',
};

uploadParts(request);
// uploadBlob({
//   method,
//   absoluteFilePath,
//   mimeType,
//   url })
//
//  becomes alias for:

uploadParts({
  method,
  parts: {
    file: {
      absoluteFilePath,
      mimeType,
      type: 'file',
    }
  },
  url,
});

Depend less on GitHub actions

At the moment, all build and test steps are defined in ci.yml. These steps need to be moved to proper build and test scripts, maybe even a Makefile, which in turn should be called from ci.yml.

I used React Native Navigation to have a kotlin Version conflict in this library. The following error occurred

I used React Native Navigation to have a kotlin Version conflict in this library. The following error occurred:

e: /Users/devin/Develop/app/KLT/node_modules/react-native-blob-courier/android/src/main/java/io/deckers/blob_courier/upload/UploaderParameterFactory.kt: (180, 13): Cannot infer a type for this parameter. Please specify it explicitly.
e: /Users/devin/Develop/app/KLT/node_modules/react-native-blob-courier/android/src/main/java/io/deckers/blob_courier/upload/UploaderParameterFactory.kt: (181, 12): Cannot infer a type for this parameter. Please specify it explicitly.
e: /Users/devin/Develop/app/KLT/node_modules/react-native-blob-courier/android/src/main/java/io/deckers/blob_courier/upload/UploaderParameterFactory.kt: (187, 13): Cannot infer a type for this parameter. Please specify it explicitly.
e: /Users/devin/Develop/app/KLT/node_modules/react-native-blob-courier/android/src/main/java/io/deckers/blob_courier/upload/UploaderParameterFactory.kt: (192, 36): Type mismatch: inferred type is ReadableMap? but ReadableMap was expected
e: /Users/devin/Develop/app/KLT/node_modules/react-native-blob-courier/android/src/main/java/io/deckers/blob_courier/upload/UploaderParameterFactory.kt: (193, 36): Type mismatch: inferred type is ReadableMap? but ReadableMap was expected
e: /Users/devin/Develop/app/KLT/node_modules/react-native-blob-courier/android/src/main/java/io/deckers/blob_courier/upload/UploaderParameterFactory.kt: (227, 15):
Cannot infer a type for this parameter. Please specify it explicitly.

I changed the kotlin Version 1.4.21 and the following error occurred again.

Execution failed for task ':app:checkDebugDuplicateClasses'.

A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
Duplicate class kotlinx.coroutines.debug.AgentPremain found in modules jetified-kotlinx-coroutines-core-1.3.7 (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7) and jetified-kotlinx-coroutines-debug-1.3.2 (org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2)
Duplicate class kotlinx.coroutines.debug.AgentPremain$installSignalHandler$1 found in modules jetified-kotlinx-coroutines-core-1.3.7 (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7) and jetified-kotlinx-coroutines-debug-1.3.2 (org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2)
Duplicate class kotlinx.coroutines.debug.internal.DebugProbesImpl found in modules jetified-kotlinx-coroutines-core-1.3.7 (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7) and jetified-kotlinx-coroutines-debug-1.3.2 (org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2)
Duplicate class kotlinx.coroutines.debug.internal.DebugProbesImpl$$special$$inlined$sortedBy$1 found in modules jetified-kotlinx-coroutines-core-1.3.7 (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7) and jetified-kotlinx-coroutines-debug-1.3.2 (org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2)
Duplicate class kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner found in modules jetified-kotlinx-coroutines-core-1.3.7 (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7) and jetified-kotlinx-coroutines-debug-1.3.2 (org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2)
Duplicate class kotlinx.coroutines.debug.internal.DebugProbesImpl$probeCoroutineCreated$frame$1$1 found in modules jetified-kotlinx-coroutines-core-1.3.7 (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7) and jetified-kotlinx-coroutines-debug-1.3.2 (org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2)

https://wix.github.io/react-native-navigation/docs/installing

Support for verbatim multipart/related S/MIME uploads

The library already supports 2 variants of multipart uploads, which covers most use-cases.

I have a third use-case now and wonder if this can be already done with the library or would become a feature request.

I have to upload S/MIME multipart/related bodies which I already prepare myself and place them in the apps storage folder like this YYYYMMDDHHMMSSMS-<guid>.upload The content of this file is the multipart/related body that needs to be uploaded. It must not be base64 encoded or anything.

Does this already work when pointing the request to this local file and setting the Content-Type header to multipart/related?

Release version 0.9.2

Version 0.9.0 of the release will contain (Android and iOS, unless stated differently):

  • Download
  • Download using manager (Android only)
  • Upload
  • Progress indicators
  • Working CI
  • Working tests

Improve on CI build time

I suspect CI build time can be improved on significantly, by:

  • Caching node_modules
  • Caching Gradle dependencies
    - [x] Caching Cocoapods

Actually disabled Cocopoads step again, because it hurt performance. Same goes for Gradle caching on Android unit tests; caching is enabled for instrumented Android tests, though.

Multipart fields arrive in unpredictable order

Originally reported by @skyliwq in #84

Describe the bug
On sending a multipart upload request, fields are ordered unpredictably (mostly lexicographically by their names).

To Reproduce
Steps to reproduce the behavior:

  1. Create a multipart upload request, with at least two fields[1]
  2. Start netcat nc -l 8001 on your connected debugging machine
  3. Notice that the aaaaa field arrived before bbbbb even though they were added to the request in opposite order

[1] An example multipart request that exposes the behavior:

const uploadResult = await BlobCourier
 .uploadParts({
   method: 'POST',
   parts: {
    bbbbb: {
     payload: 'test',
     type: 'string',
    },
    aaaaa: {
     payload: {
      absoluteFilePath: props.fromLocalPath,
      mimeType: 'text/plain',
     },
     type: 'file',
    },
   },
   url: 'http://<ip_of_your_debugger_machine>:8001',
 });

Expected behavior
Parts are sent to the server in the order they were added to the dictionary

Smartphone

  • All devices, all versions of all OS'

Add directory options for iOS

  • Add directory enum for .documentDirectory, .cachesDirectory
  • Add tests

Out of scope
- [ ] Add sub directory option

Send headers

  • Send provided headers with each request
  • Verify authorization header works

The body of your POST request is not well-formed on iOS

Content-Disposition: form-data;Name="{key}"\r\n\n{value}\r\n--{boundary} sends without problems in this format, such as rn-fetch-blob. {name: 'policy', data: OSS.policy}....

my code

BlobCourier.uploadParts({
   method: 'POST',
   returnResponse: true,
   filename: imageName,
   parts: {
     policy: {
       payload: OSS.policy,
       type: 'string',
     },
     file: {
       payload: {
         absoluteFilePath: absoluteFilePath,
         mimeType: 'application/' + imageType,
       },
       type: 'file',
     },
   },
   url: OSS.host,
})

Select a proper http client for iOS

Currently the Blob Courier generates multipart http requests manually, which causes unnecessary complexity and bugs (#83, #86) and requires extra testing.

Select an Android OkHttp-equivalent for iOS and refactor the code. Requirements for the http client:

  • Must handle multipart requests
  • Must be lightweight
  • Must be maintained

Any suggestions are welcome!

Candidates

  • Alamofire

Add support for sending files as raw HTTP bodies

Originally suggested by @pke in #135

Currently react-native-blob-courier supports multipart file uploads only, but there are situations where it is desirable to send a file to the server as a raw HTTP body.

Proposed solution
Add a send(request:SendRequest) method to TypeScript code base, inspired by HttpClient.Send.

SendRequest would look something like:

interface SendRequest {
  method:string;
  absolutePath:string;
  headers:dictionary;
  returnResponse: boolean;
}

Progress

  • Add send(request:SendRequest) method to TypeScript code base
  • Add a send(method:string,absolutePath:string,headers:dictionary) method to Android code base
  • Add a send(method:string,absolutePath:string,headers:dictionary) method to iOS code base
  • Add TypeScript tests
  • Add Android tests
  • Add iOS tests

Fetched files are stored in the wrong directory on iOS

Describe the bug
Fetched files on iOS are stored in Documents directory instead of the expected Caches directory.

To Reproduce
Steps to reproduce the behavior:

  1. Fetch a file with the example app
  2. Note the uploadable file path on the upload screen

Expected behavior
As stated in the documentation, the default behavior is to download files to the Caches directory.

Desktop (please complete the following information):

  • OS: iOS
  • Version: All

Unreachable server causes crash on Android

Describe the bug
When target server is unreachable, the library causes a crash

To Reproduce
Steps to reproduce the behavior:

  1. Create an empty app
  2. Install BlobCourier
  3. Use the library to download or upload a file from or to a server that does not exist

Expected behavior
The library does not crash the app when a server is not available, and instead returns a descriptive warning.

Desktop (please complete the following information):

  • OS: Android
  • Version: All

Additional context
The fix for this issue was already on the agenda (#25), but it deserves its own ticket.

[Android] Use react-native-image-crop-picker to read the photo path error

Android 10

020-12-11 19:33:16.012 1042-3167/com.blobtest.app E/AndroidRuntime: FATAL EXCEPTION: Thread-16
    Process: com.kelapp, PID: 1042
    java.io.FileNotFoundException: file:/storage/emulated/0/Android/data/com.kelapp/files/Pictures/d2f82461-1f50-4b46-b4f5-61ae2b8d2d5a.jpg: open failed: ENOENT (No such file or directory)
        at libcore.io.IoBridge.open(IoBridge.java:496)
        at java.io.FileInputStream.<init>(FileInputStream.java:159)
        at okio.Okio.source(Okio.java:168)
        at okhttp3.RequestBody$3.writeTo(RequestBody.java:119)
        at okhttp3.MultipartBody.writeOrCountBytes(MultipartBody.java:173)
        at okhttp3.MultipartBody.writeTo(MultipartBody.java:114)
        at io.deckers.blob_courier.BlobCourierProgressRequest.writeTo(BlobCourierProgressRequest.kt:33)
        at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:72)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:254)
        at okhttp3.RealCall.execute(RealCall.java:92)
        at io.deckers.blob_courier.BlobCourierModuleKt$startBlobUpload$1.invoke(BlobCourierModule.kt:156)
        at io.deckers.blob_courier.BlobCourierModuleKt$startBlobUpload$1.invoke(Unknown Source:0)
        at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
    	Suppressed: java.net.ProtocolException: unexpected end of stream
        at okhttp3.internal.http1.Http1Codec$FixedLengthSink.close(Http1Codec.java:307)
        at okio.ForwardingSink.close(ForwardingSink.java:47)
        at okio.RealBufferedSink.close(RealBufferedSink.java:248)
        at okio.ForwardingSink.close(ForwardingSink.java:47)
        at okio.RealBufferedSink.close(RealBufferedSink.java:248)
        at kotlin.io.CloseableKt.closeFinally(Closeable.kt:60)
        		... 19 more
     Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7737)
        at libcore.io.IoBridge.open(IoBridge.java:482)
        	... 24 more

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.