vsivsi/meteor-file-collection

Can't get CORS to work on Cordova

rsmelo92 opened this issue · 13 comments

Hello, I'm trying to upload an image on cordova and I'm getting the error:
"XMLHttpRequest cannot load http://192.168.0.6:3000/gridfs/images/_resumable. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:12096' is therefore not allowed access. The response had HTTP status code 500."

Although I've setted the headers just like the documentation shows:

http: [
		{
			method: 'head',
			path: '/id/:_id',
			lookup: function (params, query) { 
				return {_id: new Mongo.ObjectID(params._id._str)};
			}, 
			handler: function (req, res, next) {
				if (req.headers && req.headers.origin) {
					res.setHeader('Access-Control-Allow-Origin', req.headers.origin );
					res.setHeader('Access-Control-Allow-Credentials', true);
				}
			next();
			}
		},
		{
			method: 'get',
			path: '/id/:_id',
			lookup: function (params, query) {

				//console.log(Mongo);
				//console.log("params._id._str:", params._id._str);

				return {_id: new Mongo.ObjectID(params._id._str)};
			},
			handler: function (req, res, next) {
				if (req.headers && req.headers.origin) {
					res.setHeader('Access-Control-Allow-Origin', '*'); // For Cordova
					res.setHeader('Access-Control-Allow-Credentials', true);
				}
				next();
	        }
		},
		{ 
			method: 'put',  // Enable a PUT endpoint
			path: '/id/:_id',
			lookup: function (params, query) {  // uses express style url params
				return {_id: new Mongo.ObjectID(params._id._str)};
			},
			handler: function (req, res, next) {
				if (req.headers && req.headers.origin) {
					res.setHeader('Access-Control-Allow-Origin', '*'); // For Cordova
					res.setHeader('Access-Control-Allow-Credentials', true);
				}
				next();
			}
		},
		{ 
			method: 'post',  // Enable a POST endpoint
			path: '/id/:_id',
			lookup: function (params, query) { 
				return {_id: new Mongo.ObjectID(params._id._str)};
			},
			handler: function (req, res, next) {
				if (req.headers && req.headers.origin) {
					res.setHeader('Access-Control-Allow-Origin', 'req.headers.origin'); // For Cordova
					res.setHeader('Access-Control-Allow-Credentials', true);
				}
				next();
			}
		},
		{
			method: 'options',  // Enable an OPTIONS endpoint (for CORS)
	        path: '/id/:_id',
			lookup: function (params, query) {  // uses express style url params
				return {_id: new Mongo.ObjectID(params._id._str)};
			},
	        handler: function (req, res, next) {  // Custom express.js handler for OPTIONS
	           res.writeHead(200, {
	              'Content-Type': 'text/plain',
	              'Access-Control-Allow-Origin': 'req.headers.origin',  // For Cordova
	              'Access-Control-Allow-Credentials': true,
	              'Access-Control-Allow-Headers': 'user-agent',
	              'Access-Control-Allow-Methods': 'PUT,POST, HEAD, GET'
	           });
	           res.end();
	           return;
	        }
      }
	],

And I've setted this in the mobile-configs.js
App.accessRule('blob:*'); App.accessRule('*');

I still cant get it to work, can someone help me?

Hi, I'm not a Cordova expert, but if you are using resumable, it has a different URI endpoint.

Instead of /id/:_id

It uses /_resumable

So you'll probably need to add an OPTIONS enpoint for that path as well.

Thank you for your fast answer and of course for this great package, but I'm still unable to fix this...
I've done what you suggested but my app is never setting the headers with this option setted:
`{

method: 'options', 
            path: '/_resumable',
        handler: function (req, res, next) {   
           res.writeHead(200, {
              'Content-Type': 'text/plain',
              'Access-Control-Allow-Origin': 'req.headers.origin', 
              'Access-Control-Allow-Credentials': true,
              'Access-Control-Allow-Headers': 'user-agent',
              'Access-Control-Allow-Methods': 'PUT,POST, HEAD, GET'
           });
           res.end();
           return;
        }
  }`

EDIT:
now I can get to set the headers but I'm getting this error, and I cant find it anywhere:
'Error in Dicer, no file found in POST'
what does that means?

Dicer is the MIME-multipart parser that file-collection uses to handle POST requests from resumable.js on the server-side.

That error is emitted from:

d.on 'finish', () ->
unless fileStream
handleFailure "Error in Dicer, no file found in POST"

This case indicates that a POST request is coming in, but there is no file data encoded in the request data. The POST is built-up on the client side by resumable.js, file-collection doesn't touch that, so I'm guessing that there's still something wrong with the Cordova HTTP configuration such that the request body isn't being transmitted properly.

I'm not a Cordova developer, so I don't know what tools you have to debug this, but you somehow need to be able to inspect the HTTP POST requests coming out of resumable.js on the client to see what they look like, and verify that they are being transmitted correctly.

Apparently my resumable here is making a GET not a POST, because all the console logs are coming from the get only... It never passes through the POST consoles log.

********GET********
I20170414-17:51:02.838(-3)? --->> params {}
I20170414-17:51:02.838(-3)? --->> query { resumableChunkNumber: '1',
I20170414-17:51:02.839(-3)?   resumableChunkSize: '1048576',
I20170414-17:51:02.839(-3)?   resumableCurrentChunkSize: '16117',
I20170414-17:51:02.839(-3)?   resumableTotalSize: '16117',
I20170414-17:51:02.840(-3)?   resumableType: 'image/jpeg',
I20170414-17:51:02.840(-3)?   resumableIdentifier: '2519e10fd606753910f409c1',
I20170414-17:51:02.840(-3)?   resumableFilename: '1492203042655-cropped.jpg',
I20170414-17:51:02.840(-3)?   resumableRelativePath: '1492203042655-cropped.jpg',
I20170414-17:51:02.840(-3)?   resumableTotalChunks: '1' }

it goes through HEAD, then GET, then OPTIONS, maybe I'm doing something wrong? Should it pass through only POST instead of GET? How can I do it?

Hi, between the facts that I'm not a Cordova dev, I can't see your code, and I'm not the author of (nor do I support) the client-side of resumable.js, I don't think I can be of much more help to you.

From what I can tell, the problem here has something to do with the interaction between Cordova and resumable, neither of which I can support myself. I wish I had a better answer. You might try searching the issues in the resumable.js repo, others may have run into this and figured out a solution.

Alright, Thank you for your support and your time!

Sorry, just one more thing, is there any example of this package inserting image using server side nodejs stream?

If you want to start with your own stream, rather than a file path, you can look to the source for this method here: https://github.com/vsivsi/meteor-file-collection/blob/master/src/gridFS_server.coffee#L342-L352

It's coffeescript, but a pretty simple function...

Thank you once again for your amazing help!

Hey it's me again. We were able to find a solution. It needed two important steps.
First the headers that worked for me were different from the readme:

http: [
		{
			method: 'get',
			path: '/id/:_id',
			lookup: function (params, query) {
				return {_id: new Mongo.ObjectID(params._id._str)};
			},
		},
		{
		    method: 'post',  // Enable a POST endpoint
            path: '/_resumable',  // this will be at route "/gridfs/images/_resumable"
            lookup: function (params, query) {  // uses express style url params
                return { };       // a query mapping url to images
            },
            handler: function (req, res, next) {
                if (req.headers && req.headers.origin) {
                    res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // For Cordova
                    res.setHeader('Access-Control-Allow-Credentials', true);
                }
                next();
            }
        },
		{
			method: 'head',  // Enable an HEAD endpoint (for CORS)
	        path: '/_resumable',  // this will be at route "/gridfs/images/_resumable/"
	        lookup: function (params, query) {  // uses express style url params
	            return { };       // a query mapping url to images
	        },
	        handler: function (req, res, next) {  // Custom express.js handler for HEAD
	           if (req.headers && req.headers.origin) {
                    res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // For Cordova
                    res.setHeader('Access-Control-Allow-Credentials', true);
                }
	            next();
	        }
	    },
		{
			method: 'options',  // Enable an OPTIONS endpoint (for CORS)
	        path: '/_resumable',  // this will be at route "/gridfs/images/_resumable/"
	        lookup: function (params, query) {  // uses express style url params
	            return { };       // a query mapping url to images
	        },
	        handler: function (req, res, next) {  // Custom express.js handler for OPTIONS
	            res.writeHead(200, {
	                'Content-Type': 'text/plain',
	                'Access-Control-Allow-Origin': req.headers.origin,  // For Cordova
	                'Access-Control-Allow-Credentials': true,
	                'Access-Control-Allow-Headers': 'x-auth-token, user-agent',
	                'Access-Control-Allow-Methods': 'GET, POST, HEAD, OPTIONS'
	            });
	            res.end();
	            return;
	        }
	    }
	],

And the second step was that we were trying to upload a file type, and the code only worked when we converted it to a blob type. So if someone is struggling with the same problem try this: change the http headers and use a blob not a file!

Great news! Thanks for following-up to let me and future Cordova devs know what worked for you!

If you want to submit a PR to update/extend the docs for CORS/Cordova, I'd happily accept it!

Sorry for taking too long, of course, I will submit a PR to help with that!