XEP 0363: HTTP File Upload support
patelvanita360 opened this issue · comments
i have setup kaiwa in local server, for backup i am using ejabberd, when i try to share image in chat its not working as well as call feature also not working
Hello,
Is there any update on this issue ?
Do you have any logs in js console? (try to set localStorage.debug = true
to get more verbose output)
I am sending following request for image sharing in one to one chat :
client.use(shareMedia); // plugin created for share media
var message = {
from:me.jid,
id:client.nextId(),
to: 'upload.domainname',
type: 'get',
request: {
filename: file.name,
size: file.size
}
};
client.mediaPushService(message).then(function (err,data) {
if(err)console.log(err);
console.log('image upload');
}).catch(function (err) {
console.log('image not upload');
});
Response i am getting from xampp server(ejabberd i am using ) :
<<in iq xmlns='jabber:client' xml:lang='en' to='8000955974@uploaddomain/2497367150272585652773' from='upload.domain' type='result' id='19b5807f-0b10-46a3-b037-e1c9f46668c0
slot xmlns='urn:xmpp:http:upload:0'
get url='https://uploaddomain/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/
put url='https://uploaddomain/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/
slot
/iq
I could not find image on given slot.
Note : i have removed response xaml "<" and " >" tags because it was not printing here
Hello,
Is there any update on this issue ?
Honestly I don't understand what's going on. Why are you showing us the JS code? Is it Kaiwa code? Are you somehow patching it? I wasn't able to find the code you're referencing in our code base. What is it exactly?
@patelvanita360 if you are trying to implement http upload support in Kaiwa, then you should follow http upload XEP. From my quick view - you are requesting upload slot and receive urls for GET and PUT, now you need to PUT image first, and then it will be available on GET url
Just use Fetch API
Can you please check following :
I got error while use fetch API
Fetch API cannot load 'PUT URL'. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'LOCAL SERVER' is therefore not allowed access. The response had HTTP status code 405. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
You must enable CORS header on HTTP URLs on your server side to make HTTP requests from browser applications
i have enabled cors header and i am using POST method to upload data on given put URL
its display error like
405 (Method Not Allowed)
Sure, because it allows only HTTP PUT method
But when i am using PUT methos its display
TypeError: Failed to execute 'fetch' on 'Window': 'PUT' is unsupported in no-cors mode
I am uploading image from local server
As your server supports CORS, you should NOT add no-cors parameter
Ok Thank you so much for your valuable time now it seems like working ....
One more question I have that how can I fetch PUT URL to make it dynamic from ejabberd response ?
I am getting log like this :
<<in iq xmlns='jabber:client' xml:lang='en' to='8000955974@uploaddomain/2497367150272585652773' from='upload.domain' type='result' id='19b5807f-0b10-46a3-b037-e1c9f46668c0
slot xmlns='urn:xmpp:http:upload:0'
get url='https://uploaddomain/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/
put url='https://uploaddomain/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/
slot
/iq
Note : i have removed response xaml "<" and " >" tags because it was not printing here
Not sure I'm understand your question. What is "make it dynamic"?
My current code to upload image data on server :
fetch('https://domain:5444/bbe52498d96b7edc6d45cab8575fbf43f4604754/Gyv1fZadeQWnmzLVOgFamnKkDvphH5hVENOLrkH5/app-icon.png', {
method: 'PUT',
mode: 'cors',
header : header,
body: data
})
I want to pass real time PUT URL , currently i just add static , but i can not get that url in response.
There is an example plugin for stanza.io - in your plugin you should define <slot xmlns='urn:xmpp:http:upload:0' ... />
extension with all required fields and they will automatically parsed by stanza.io.
Here is my quick example:
let jxt = require('jxt').createRegistry();
jxt.use(require('jxt-xmpp-types'));
jxt.use(require('jxt-xmpp'));
let helpers = jxt.utils;
let UPLOAD_NS = 'urn:xmpp:http:upload:0';
let UploadSlot = jxt.define({
name: 'uploadSlot',
namespace: UPLOAD_NS,
element: 'slot',
fields: {
get: helpers.subAttribute(UPLOAD_NS, 'get', 'url'),
put: helpers.subAttribute(UPLOAD_NS, 'put', 'url')
}
});
jxt.extendIQ(UploadSlot);
var result = jxt.parse("<iq xmlns='jabber:client' xml:lang='en' to='8000955974@uploaddomain/2497367150272585652773' \
from='upload.domain' type='result' id='19b5807f-0b10-46a3-b037-e1c9f46668c0'>\
<slot xmlns='urn:xmpp:http:upload:0'>\
<get url='https://uploaddomain/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/>\
<put url='https://uploaddomain/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/>\
</slot>\
</iq>");
console.log("PUT URL is " + result.uploadSlot.put)
which outputs correct url:
PUT URL is https://uploaddomain/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png
client.sendIQ
have second callback parameter (error, response)
where response is the reply to your slot request you passed as first parameter
I have tried a lot in a different different way but Not getting luck. not getting a response which I want, i am attaching my whole code here
kaiwa/src/js/pages
var shareMedia = require('../helpers/shareMedia');
events: {
'change #files': 'handleMediaClick',
},
handleMediaClick: function(e){
e.preventDefault();
var self = this;
console.log(this.model.topResource);
var file;
if (e.dataTransfer) {
file = e.dataTransfer.files[0];
} else if (e.target.files) {
file = e.target.files[0];
} else {
return;
}
if (file.type.match('image.*')) {
var downloadURL = URL.createObjectURL(file);
console.log('Offering:', file.name, file.size, file.type, file.lastModifiedDate, downloadURL);
var fileTracker = new FileReader();
fileTracker.onload = function () {
console.log('file', file.name, file.size, file.type, file.lastModifiedDate);
var data = this.result;
var resampler = new Resample(data, 80, 80, function (data) {
var b64Data = data.split(',')[1];
var id = crypto.createHash('sha1').update(atob(b64Data)).digest('hex');
client.use(shareMedia);
var message = {
from:me.jid,
id:client.nextId(),
to: 'upload.domain',
type: 'get',
request: {
filename: file.name,
size: file.size
}
};
client.mediaPushService(message).then(function (result) {
console.log("PUT URL is " + resp);
/*var imageupload = {
from:me.jid,
id:client.nextId(),
to: 'https://domain:5444/106da9efb664468e26bca974aaa2fe38cc7c0785/1cvz5Xz6IvPnZY0dY1Iifytd3h95i0a3WZoNOjZk/111newssssss11.png',
type:'set',
request: {
data: b64Data
}
};
client.uploadMedia(imageupload);*/
if (!('fetch' in window)) {
console.log('Fetch API not found, try including the polyfill');
return;
}
let header = new Headers({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
'Content-Type': 'multipart/form-data'
});
fetch('https://domain:5444/bbe52498d96b7edc6d45cab8575fbf43f4604754/Gyv1fZadeQWnmzLVOgFamnKkDvphH5hVENOLrkH5/app-icon.png', {
method: 'PUT',
mode: 'cors',
header : header,
body: id
})
if(err)console.log(err);
client.sendMessage({
id: client.nextId(),
type:'chat',
to: self.model.topResource,
body:b64Data
});
console.log('Could enable push notifications');
}).catch(function (err) {
console.log('Could not enable push notifications');
});
});
}
fileTracker.readAsDataURL(file);
}
},
kaiwa/src/js/helpers/shareMedia.js
"use strict";
module.exports = function (client, stanzas) {
var types = stanzas.utils;
var shareMedia = stanzas.define({
name: 'request',
element: 'request',
namespace: 'urn:xmpp:http:upload:0',
fields: {
size: types.attribute('size'),
filename: types.attribute('filename')
}
});
stanzas.withIq(function (Iq) {
stanzas.extend(Iq, shareMedia);
});
client.mediaPushService = function (message) {
return client.sendIq(message);
};
};
client.mediaPushService = function (message) {
return client.sendIq(message);
};
I did not see you are using sendIq callback
i had use like this , but its not working
client.mediaPushService = function (message) {
client.sendIq(message,function(error,response){
console.log(response);
});
}
what is in log?
And I didn't see slot urls definition in your shareMedia stanza extension
shareMedia.js
`"use strict";
module.exports = function (client, stanzas) {
var types = stanzas.utils;
let jxt = require('jxt').createRegistry();
jxt.use(require('jxt-xmpp-types'));
jxt.use(require('jxt-xmpp'));
let helpers = jxt.utils;
let UPLOAD_NS = 'urn:xmpp:http:upload:0';
var shareMedia = stanzas.define({
name: 'request',
element: 'request',
namespace: 'urn:xmpp:http:upload:0',
fields: {
size: types.attribute('size'),
filename: types.attribute('filename')
}
});
stanzas.withIq(function (Iq) {
stanzas.extend(Iq, shareMedia);
});
var uploadMediaData = stanzas.define({
name: 'uploadMediaData',
namespace: 'urn:xmpp:push:0',
element: 'request',
fields: {
data: types.text()
}
});
stanzas.withIq(function (Iq) {
stanzas.extend(Iq, uploadMediaData);
});
client.uploadMedia = function (message) {
return client.sendIq(message);
};
let UploadSlot = jxt.define({
name: 'uploadSlot',
namespace: UPLOAD_NS,
element: 'slot',
fields: {
get: helpers.subAttribute(UPLOAD_NS, 'get', 'url'),
put: helpers.subAttribute(UPLOAD_NS, 'put', 'url')
}
});
jxt.extendIQ(UploadSlot);
client.mediaPushService = function (message) {
client.sendIq(message,function(error,response){
console.log("jxt parsing :" + jxt.parse(response));
var result = jxt.parse("<iq xmlns='jabber:client' xml:lang='en' to='8000955974@upload.domain/2497367150272585652773' from='upload.domain' type='result' id='19b5807f-0b10-46a3-b037-e1c9f46668c0'><slot xmlns='urn:xmpp:http:upload:0'><get url='https://domain:5444/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/><put url='https://domain:5444/975fb8db938152f973ef26ba56681228d5296634/WgaDXKEx67McKPPnGHMIOGzssRzkalozdK1cRHMQ/boy.png'/></slot></iq>");
console.log("PUT URL is " + result.uploadSlot.put);
});
}
};`
chat.js
` handleMediaClick: function(e){
e.preventDefault();
var self = this;
console.log(this.model.topResource);
var file;
if (e.dataTransfer) {
file = e.dataTransfer.files[0];
} else if (e.target.files) {
file = e.target.files[0];
} else {
return;
}
if (file.type.match('image.*')) {
var downloadURL = URL.createObjectURL(file);
console.log('Offering:', file.name, file.size, file.type, file.lastModifiedDate, downloadURL);
var fileTracker = new FileReader();
fileTracker.onload = function () {
console.log('file', file.name, file.size, file.type, file.lastModifiedDate);
var data = this.result;
var resampler = new Resample(data, 80, 80, function (data) {
var b64Data = data.split(',')[1];
var id = crypto.createHash('sha1').update(atob(b64Data)).digest('hex');
client.use(shareMedia);
var message = {
from:me.jid,
id:client.nextId(),
to: 'upload.domain',
type: 'get',
request: {
filename: file.name,
size: file.size
}
};
client.mediaPushService(message).then(function (result) {
console.log("PUT URL is " + result);
if (!('fetch' in window)) {
console.log('Fetch API not found, try including the polyfill');
return;
}
let header = new Headers({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
'Content-Type': 'multipart/form-data'
});
fetch('https://domain:5444/bbe52498d96b7edc6d45cab8575fbf43f4604754/Gyv1fZadeQWnmzLVOgFamnKkDvphH5hVENOLrkH5/app-icon.png', {
method: 'PUT',
mode: 'cors',
header : header,
body: id
})
if(err)console.log(err);
client.sendMessage({
id: client.nextId(),
type:'chat',
to: self.model.topResource,
body:b64Data
});
console.log('Could enable push notifications');
}).catch(function (err) {
console.log('Could not enable push notifications');
});
});
}
fileTracker.readAsDataURL(file);
}
}, `
- no need to import jxt, as required JXT object is passed to module as
stanzas
- your
mediaPushService
is not a Promise, so you can not usethen
there, that is what console say to you
i have done changes and got final log like this
PUT https://domain:5444/975fb8d…/eKTEWJXTffFGwokq8WW2SXJXxQhkU0IvTE9SPaaW/girl.png 413 (Request Entity Too Large)
:8000/#chat/8000955972%40domain:1 Fetch API cannot load https://domain:5444/975fb8d…/eKTEWJXTffFGwokq8WW2SXJXxQhkU0IvTE9SPaaW/girl.png. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.1.28:8000' is therefore not allowed access. The response had HTTP status code 413. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
:8000/#chat/8000955972%40domain:1 Uncaught (in promise) TypeError: Failed to fetch
413 (Request Entity Too Large)
That is your server configuration
i have set headers on server , can you suggest me which other configuration need to change ?
Entity too large means your upload limit too low
that solved but still getting this
Fetch API cannot load https://domain:5444/975fb8db938152f973ef26ba56681228d5296634/kwjCxd9nhAk5KILEavBhR1EcvsCQ57uNixE07qQO/app-icon.png. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.1.28:8000' is therefore not allowed access. The response had HTTP status code 413. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
:8000/#chat/8000955972%40domain:1 Uncaught (in promise) TypeError: Failed to fetch
which change i need to do on server side? can you please tell me
is my file upload code correct to upload file using fetch api
i am near to finish this feature can you please support me more?
Error message clearly states it does not found cors header on server
My server configuration :
mod_http_upload:
docroot: "@HOME@/upload"
put_url: "https://@HOST@:5444"
max_size: 5000000 #5MB
custom_headers:
"Access-Control-Allow-Origin": "*"
"Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS, DELETE"
"Access-Control-Allow-Headers": "Content-Type, Origin, X-Requested-With"
thumbnail: false # otherwise needs ejabberd to be compiled with libgd support
mod_http_upload_quota:
max_days: 30
mod_last: {}
I did not see any reference to custom_headers in mod_http_upload source code, so I think you need to fix that module first
https://docs.ejabberd.im/admin/configuration/#mod-http-upload
i have setup custom headers from here
is there any reference link to fix this ?
Nevermind, I forgot you are using ejabberd
I think you should configure your PUT url as put_url: "https://@HOST@/upload:5444"
as module maybe adds headers only to docroot
urls
You also miss host
configuration which is upload.@HOST@
by default, and your PUT url may should match host url to set custom_headers
still got same error , is there any other method to upload image data ?
That is your server misconfiguration, ask ejabberd support on that
trying to ping @weiss :)
The response had HTTP status code 413.
mod_http_upload
will return this code if the size of the file uploaded with the PUT request doesn't match the size specified in the PUT request. ejabberd will write an [info]
message to your ejabberd.log in that case, please always check the logs when running into trouble.
@weiss I think he set upload limits and now only CORS headers are not configured
I think he set upload limits and now only CORS headers are not configured
But the 413 status is mentioned in the last error message he quoted. The CORS headers are only included in the success case.
@weiss yes, i resolved this limit issue, CORS issue is there
Now i am getting following error:
Fetch API cannot load https://domain:5444/975fb8db938152f973ef26ba56681228d5296634/ACiyS38WrF3EzxyMdUz7W0H9Y9i9yhymivFY42Jm/app-icon__1_.png. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.1.28:8000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
:8000/#chat/8000955972%40domain:1 Uncaught (in promise) TypeError: Failed to fetch
Response to preflight request
Preflight request is OPTIONS request, need to check is it really handled my mod_http_upload
Now i am getting following error: [...]
So the message is now different from your last quote. This doesn't help with debugging.
What's your ejabberd_http
listener configuration for port 5444? Also, what ejabberd version are you using?
Preflight request is OPTIONS request, need to check is it really handled my mod_http_upload
Yes mod_http_upload
responds to OPTIONS
requests.
listener
port: 5444
ip: "::"
module: ejabberd_http
request_handlers:
"": mod_http_upload
tls: true
certfile: 'CERTFILE'
protocol_options: 'TLS_OPTIONS'
http_bind: true
mod_http_upload:
docroot: "@HOME@/upload" put_url: "https://@HOST@:5444" max_size: 5000000 #5MB custom_headers: "Access-Control-Allow-Origin": "*" "Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS, DELETE" "Access-Control-Allow-Headers": "Content-Type, Origin, X-Requested-With" thumbnail: false # otherwise needs ejabberd to be compiled with libgd support
mod_http_upload_quota:
max_days: 30
ejabberd version : 18.03
Could you open an ejabberdctl debug
shell and copy the output of the following call?
gen_mod:get_module_opt(<<"example.com">>, mod_http_upload, custom_headers).
Replace example.com
with your domain, don't forget the trailing .
, and press Ctrl
+C
twice when you're done.
ejabberd@db01)1> gen_mod:get_module_opt(<<"domain.com">>, mod_http_upload, custom_headers).
[{<<"Access-Control-Allow-Origin">>,<<"*">>},
{<<"Access-Control-Allow-Methods">>,
<<"GET, POST, PUT, OPTIONS, DELETE">>},
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type, Origin, X-Requested-With">>}]
Looks good, except that you should remove POST
and DELETE
from the methods list, and add HEAD
.
Your last error message doesn't mention the request type nor the response code. Maybe the request failed for some reason and the CORS headers are missing from the error response. There's nothing in your ejabberd.log
regarding this request?
You could create a file in your upload directory and check whether the CORS headers are included with a GET request using another client, e.g.:
mkdir -p ~ejabberd/upload/foo/bar && echo hi > ~ejabberd/upload/foo/bar/test.txt
curl -i https://example.com:5444/foo/bar/text.txt
rm -r ~ejabberd/upload/foo
Replace ejabberd
with the name of the user running ejabberd (though often this is the name), and of course example.com
with your domain name.
@weiss shouldn't mod_http_upload automatically set correct CORS headers without any custom_headers
directives?
@vitalyster, not sure you want Access-Control-Allow-Origin: *
in all environments, but maybe.
@weiss prosody's module have cross_domain
option accepts 'true' or list of domains
@weiss
Here you can see ejabberd log. https://pastebin.com/DADZWZrB
I have doubt in this line:
2018-05-07 08:49:52.008 [info] <0.16110.1>@mod_http_upload:process:409 Rejecting file from ::ffff:123.567.8.321 for domain: Unexpected size (10182)
I have already defined "max_size: 5000000 #5MB" Not sure which size it's taking there.
Rejecting file from ::ffff:123.567.8.321 for domain: Unexpected size (10182)
This is the case where the module returns a status of 413. As I told you in my very first response, this is not about hitting a limit; the problem is that the size you're specifying in the slot request doesn't match the size of the data you're submitting with the PUT request (the latter is 10182 bytes).
Please always check the logs when running into trouble.
Thank you @weiss and @vitalyster for your valuable support .
now image uploaded but I can not see that image in browser.
Here is the logs https://pastebin.com/mbsLtWbf
Can you please check it what can be issue ?
Show latest sending code, please
https://pastebin.com/YdZhQK07
here is my latest code for image data format
how did you read file and create b64data variable? If it is a base64 encoded - it's wrong, you need to send plain octet stream
your file data is in data
variable, you should send slot request for data.length
and PUT body must be data
too
data variable contain data like "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA......."
OK, so you need to decode it first and use correct decoded size in slot request and decoded data in PUT request
@vitalyster , @weiss
Finally its working now. Thank you so much again for your quick support. :)
will you contribute you work back to Kaiwa, or we spend our time for free? :)
:) yes sure. will contribute it
@vitalyster , merge request sent, please check and accept it. :)
Can you please tell me where can I call sharemedia plugin globally ?
Currently i am calling plugin on media change event.
Not sure I am understand the question, please elaborate?
If I understand correctly, you want to enable plugin, it should be done there: https://github.com/ForNeVeR/Kaiwa/blob/master/src/js/app.ts#L110
Yes exactly but when i initialize in app.ts file , plugin is not working ...
Error messages?
sharemedia not defined
shareMedia
is the variable where your plugin loaded, you should move var shareMedia = require('../helpers/shareMedia');
line too. Note, you need to change path to be relative to app.ts
now its working , thank you . :)
can you update your pull request?
Yes , It's done.
Thanks! let's discuss it in the PR itself
Wait, was this implemented after all?
@stevenroose work in progress there
@vitalyster is there maybe a MUC where you guys hang out? :)
@vitalyster I have raise one query , can you please update me there so i can work on it further.