couchbaselabs / react-native-couchbase-lite

Couchbase Lite binding for React Native Android & iOS
MIT License
111 stars 54 forks source link

Images in Example project as Couchbase Lite attachments #11

Closed jamesnocentini closed 8 years ago

jamesnocentini commented 8 years ago

Test that images can be inserted as attachments and replicated accordingly. /cc @ssomnoremac

ssomnoremac commented 8 years ago

@jamiltz it seems that you are right, it works to use the attachment as a uri like so

<Image 
          source={{uri: "http://localhost:5984/myapp/100/pic100.jpg"}} 
          style={styles.thumbnail} />

that's great! But when I looked at the device database it hadn't replicated the attachment from sync_gateway so that's why it's not showing up. Let me double check and restart sync and try again

jamesnocentini commented 8 years ago

:+1: Ok let me know if the replication works

ssomnoremac commented 8 years ago

Nevermind, it's there. Still not working though so it's something else.

jamesnocentini commented 8 years ago

What is there? And what is not working just to be clear on what I should run.

ssomnoremac commented 8 years ago

so on my forwarded port i can see this http://localhost:5984/myapp/100/pic100.jpg is definitely there. However, the above Image component is not working

jamesnocentini commented 8 years ago

Ah ok that's a pity. Can you push the code on a branch on your fork?

ssomnoremac commented 8 years ago

definitely, I'll try and do that soon. but I did a curl to get the img up so it will be hard for you to replicate my exact sync_gateway. You'll have to curl an attachment in yourself.

ssomnoremac commented 8 years ago

yeah, so it's just one line

and to curl in an image something like this (but you'll have to add a document with id of 100)

curl -v -X PUT http://127.0.0.1:4984/moviesapp/100/pic100.jpg?rev=[rev # here] --data-binary @pic100.jpg -H "Content-Type: image/jpg"
jamesnocentini commented 8 years ago

Thanks that's great I'll have a look tomorrow.

ssomnoremac commented 8 years ago

I've done a little testing

So perhaps CouchbaseLite is adding a https-like security layer? Can the response headers for couchbase lite be modified? CouchDB is specifying content type. Perhaps that is needed.

ssomnoremac commented 8 years ago

@jamiltz almost certain this is a CORS issue on the lite server. Can you advise how to allow *

jamesnocentini commented 8 years ago

@ssomnoremac sorry for the lack of response. Could be! What makes you think CORS? I've was biten so many times by it ;) Have you checked the request headers with httpscoop or charles?

ssomnoremac commented 8 years ago

@jamiltz I couldn't figure out how to sniff the http request between my app and the the cbLite server using Charles. I'm not too familiar with Maven but, assuming this is CORS, making this kind of modification would require forking the couchbase maven repo or pulling the entire cb code into the app, correct?

louisruch commented 8 years ago

@jamiltz, @ssomnoremac So the issue and reason we think it's a CORS issue is firstly as you mentioned this has bitten us before :) We had basically the exact same problem when developing our Angular app on CouchDB. From the browser in chrome we are able to get access to the attachment using the same URI as in the react native app. Now the reason CORS exists is to control exactly what we are trying to do - take an asset from service x and re serve it.

Looking at line 60 of the LiteServlet I see the only header being added is www-auth, ideally we would add origin here to remove our expected problem. The perfect way would be to pass all the headers we want similarly to how credentials is done!

https://github.com/couchbase/couchbase-lite-java-listener/blob/release/1.1.0/src/main/java/com/couchbase/lite/listener/LiteServlet.java

I have played around with this a bit and cannot seem to find any other reason other than CORS. Changing our code to request the URI from our couchDB AWS hosted server presents the image perfectly.

jamesnocentini commented 8 years ago

I finally got down to reproducing the issue and it turns out to be the Image component stripping out the credentials from the url to which CBL Listener returns a 401 Unauthorized. So http://admin:pass@localhost... becomes http://localhost....

I see two possible workarounds:

N.B: I recommend using Charles for network debugging the CBL REST API on a device or emulator. Start Charles then from the Android WiFi page, select the current network and add a manual proxy in advanced settings (set the hostname to the IP of your computer and port to 8888 - the default reverse proxy port on Charles). Return to the app and Charles should pick up the traffic going to the CBL Listener.

louisruch commented 8 years ago

Hey thanks,

Yeah was going to try base64 today anyway to see if that works and I can confirm it does - I think we will use this as a work around! Thanks @jamiltz

npomfret commented 8 years ago

Does anyone have a working example of how to PUT and image attachment to CBL using the rest API?

jamesnocentini commented 8 years ago

@npomfret did you try the workaround to base 64 encode the attachment? (see @SolmyrBhaal's implementation for reference https://github.com/InfiniteLibrary/infinite-reader/pull/16/files)

npomfret commented 8 years ago

Thanks for the reply. I'm not quite sure what I'm seeing there. Is his workaround to just attach the raw base64 data instead of binary encoding it. Basically just like attaching a text file? I've not tried that but I'm sure it would work.

I wanted to shrink the attachment size down though. I understand base64 encoding of images is quite bloated.

There is a way to do it but I can't get the libraries to work in RN

jamesnocentini commented 8 years ago

Yes you can add attachments by encoding them in base64 in the body. You can check the documentation or the code in comments above. Ideally, we could just pass a url to the Image component but as explained above, React Native strips out the auth header (need to check if it's still the case with React Native 0.23). In any case, I think we should update the ReactNativeCouchbaseLiteExample app to use the attachments for the movie thumbnails.

jamesnocentini commented 8 years ago

No update in RN 0.23 regarding this issue. Adding images via the PUT /dbname/docname/attachmentname?rev=... endpoint and retrieving them as base64 with attachments=true is the workaround. @SolmyrBhaal / @ssomnoremac did you use base64 attachments on iOS and/or Android. I found it works on iOS but the attachments=true option on Android doesn't return the attachment inlined in the json response body.

louisruch commented 8 years ago

@jamiltz we are using Android. There are two problems:

1) Get The fetch is stripping the credentials in the http get call to get the attachment so one solution is to leave the credentials blank - no user name or password when running init: ReactCBLite.init(5984, '', '', (e) => {}); The other option is the base64 example where you request the via the CBLite Manager and get the document with the base64 inline in the JSON: return catalogDB.getDesignDocument('_all_docs?include_docs=true&attachments=true')}

2) Put So the other issue we had was putting the binary data, we did resolve it as mentioned above by just including a plain/txt document which stored the encrypted base64 data - the problem here is it does give a 25-40 percent increase in the file size. The problem we had which has not been resolved was that the fetch being returned kept throwing an error when downloading and trying to upload again. We tried a number of different methods in the first then((res... blob, text, etc nothing worked - eventually we settled on the plain/txt version to get something out...

fetch(page.fullPath) .then((res) => res.text()) // Problem here .then((img) => { console.log("downloading page number ", index) if (rev === undefined) { return booksDB.createAttachment(page.docName, img, page.attachmentName, 'plain/txt') } else { return booksDB.createAttachment(page.docName, img, page.attachmentName, 'plain/txt', rev) } }) .then((res) => { dispatch(pageDownloadComplete(imgArray)) return dispatch(asyncDownloadPage(imgArray, index, res.rev)) }) .catch((err) => { throw err; })

npomfret commented 8 years ago

I think I've got something working that doesn't do the base64 attachment upload. I've had to use a package called react-native-fileupload which I understand will upload the binary data rather than the bloated base64 data. My work so far is on this fork.

The problem I've got at the moment is my getAttachment function returns a Blob. Not sure how to turn that into an image or even a file on disk yet.

louisruch commented 8 years ago

Does this plugin have support for the path variable to be another URL or must you save the attachment locally on the device file system before you can upload?

On Sunday, April 24, 2016, Nick Pomfret notifications@github.com wrote:

I think I've got something working that doesn't do the base64 attachment upload. I've had to use a package called react-native-fileupload which I understand will upload the binary data rather than the bloated base64 data. My work so far is on this fork https://github.com/npomfret/react-native-couchbase-lite/blob/master/index.js .

The problem I've got at the moment is my getAttachment function returns a Blob. Not sure how to turn that into an image or even a file on disk yet.

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/couchbaselabs/react-native-couchbase-lite/issues/11#issuecomment-213916154

Kind Regards,

Louis C. Ruch

npomfret commented 8 years ago

Not tried a remote url yet. I'm just currently stuck on getting it back out of the BCL database

louisruch commented 8 years ago

I'm not at my machine to test, but it should work fine as a blob, if the attachment type is set as 'image/jpg' or whatever it should be, png, etc

Have you tried forwarding port 5984 and accessing cblite from your machine to test?

'adb forward tcp:5984 tcp:5984'

Then from your machine just access the cblite database via browser

http://127.0.0.1:5984/dbname/docname/attachment

That way you can at least download and test the attachment and see if everything is good in a controlled environment...

On Sunday, April 24, 2016, Nick Pomfret notifications@github.com wrote:

Yes it works with a url. I'm just currently stuck on getting it back out of the BCL database

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/couchbaselabs/react-native-couchbase-lite/issues/11#issuecomment-213919852

Kind Regards,

Louis C. Ruch

npomfret commented 8 years ago

I may have spoken to soon. The upload is being accepted, but I think its not expecting a multipart form data type body, rather a stream of bytes. So the data that gets saved into CBL contains a load of http related text as well as the binary data. Need to find a way to post an http body with raw binary data in it.

npomfret commented 8 years ago

Ok, I've finally got the PUT with binary data working. I've to write some native code to stream the binary data across into CBL.

The java code, is a work in progress but it does work. The base64 workaround is still needed to view the file, but that's fine. It was the increased network overhead I was worried about.

And this implementation will take a file path or a URL as the source, so the source binary data doesn't need to be one the device.

I've never written any IOS code so this really needs another set of eyes.

npomfret commented 8 years ago

Has anyone managed to get a document with its attachments in android? My response is missing the crucial data part:

_attachments: 
  { 'fe4f4891-d90d-4fc9-9051-0541f5a9c6a7': 
    { digest: 'sha1-g9aU9ZF5SoSQw7BYXH/f5JBuO4A=',
      length: 1572,
      revpos: 2,
      stub: true,
      content_type: 'image/jpeg' } }
npomfret commented 8 years ago

The bug of the stripped auth text in the url is android only. I've raised an issue but I can't find the problem, so can't fix it... yet.

However, the binary upload works in both IOS and Android now. And the attachment URL works nicely in IOS.

jamesnocentini commented 8 years ago

Re attachments=true I think it might be because that querystring option isn't handled by the Android Listener. I've opened it at https://github.com/couchbase/couchbase-lite-java-core/issues/1210.

npomfret commented 8 years ago

Ok, thanks. So an android we're a bit stuck because we can't get attachments out either way.

If we can get the Facebook Fresco project updated so that Android can load images with auth in the urls the same way as IOS I think we're good. I'll tidy up the binary upload stuff a bit in my fork and submit it. I could really do with some help with the Objective C stuff. Especially around error handling and waiting for the upload to complete.

npomfret commented 8 years ago

I've written a bit of native code to deal with upload binary attachments. Not added instructions to the docs yet but it would be good if someone else could try it out

https://github.com/couchbaselabs/react-native-couchbase-lite/pull/42

New function is called saveAttachment, and to load the data use getAttachmentUri to generate a url. As discussed - the urls don't work for images in android yet.

jamesnocentini commented 8 years ago

Thanks! I'll give this a go. I'm curious why we need the saveAttachment method, isn't the PUT /dbname/docid/attachmentname endpoint suited for that? To be honest, I've only tried to add attachment using Postman and the nodejs request module. But I imagine that it should be possible with FormData/XmlHttpRequest/fetch.

npomfret commented 8 years ago

Hi James. Yes, its not obvious, (or I've got something very wrong!). What I found was that CBL would accept a From posted / multipart form data upload, but when I read it back the content wasn't what I expected. Both XmlHttpRequest and fetch will send a multipart form post with the file in it.

When I looked in to the code I saw that CBL is expecting the body to contain only binary data. CBL doesn't parse out the multipart form data stuff (the boundary info and blank lines), and so what gets saved to disk is your binary data + some unwanted http stuff. Which obviously come back out again when you query it.

I couldn't find another way to send binary data across using java script so...

The native method I've added here, which still need a little work but seems to do the job, just streams the contents of a url into CBL with no additional http stuff like what you get with a multipart form post. The source of the url doesn't even need to be a file on your phone.

I've tested on both devices. IOS work fine. But in Android, although the upload looks fine, as we've discussed there's an issue with RN where it doesn't add the auth headers in android so you get a 401 response when trying to download the attachment. I've tried unsuccessfully to get this fixed. Not sure what to do about it now.

Maybe add (temporarily) another native method to take an attachment and just put it on disk somewhere. Until the issue is fixed properly.

Does any of that make sense?

npomfret commented 8 years ago

I've made some updates to my PR, now https://github.com/couchbaselabs/react-native-couchbase-lite/pull/43

So it handles IOS asset type URIs (of the sort you get back from the camera roll.

If anyone has had a chance to try it out please let me know - it would be good to get merged.

ssomnoremac commented 8 years ago

@npomfret , saw all your work on this just now. I've been out of the country. Sorry I couldn't help out but it looks like you made good progress. I will definitely try this out soon.

Will your work offer a solution to core RN? This is where I went looking to find a solution.

npomfret commented 8 years ago

Thanks!

What do you mean about "Will your work offer a solution to core RN?"

ssomnoremac commented 8 years ago

Doesn't your solution (Java Code) solve the problem posed by the thread I referenced: "Does fetch with blob() marshal data across the bridge". Are you not now marshaling the binary data across the bridge?

npomfret commented 8 years ago

No, all the native code does (both IOS and android) is take a uri and stream the binary data from it to another uri as the body of a PUT or POST.

jamesnocentini commented 8 years ago

@npomfret It looks like java-core just got ?attachments=true support (https://github.com/couchbase/couchbase-lite-java-core/issues/1210) so once we've merged https://github.com/couchbaselabs/react-native-couchbase-lite/pull/43 we can try it. The latest nightly build of cbl android should have this change (http://latestbuilds.hq.couchbase.com/couchbase-lite-android/0.0.0/)

npomfret commented 8 years ago

Ok, i'll try and get it merged today.

npomfret commented 8 years ago

I'm going to close this ticket as I believe the new release copes with binary attachments of any sort.

npomfret commented 8 years ago

Looks like the Android OkHttp library that react-native uses on Android doesn't support urls containing basic auth parameters and so images coming from the CBL database won't be displayed.

https://github.com/square/okhttp/issues/2143

There was talk of them fixing it in OkHttp. As of the latest RN 0.27 (which I believe got a new version of OkHttp) it is still not working.

bjornd commented 6 years ago

For anyone having problems with attachments in react-native-couchbase-lite I would like to suggest to try a new React Native module for Couchbase Lite react-native-cbl. It implements all the methods via native code calls providing better performance and richer feature set.