ForNeVeR / Kaiwa

A modern XMPP Web client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

Yes I got it but still you don't understand my query. Let me raise query from your last replay so i will get my ans
var result = jxt.parse("HOW CAN I GET THIS DATA");

i am getting this data in response
uploadslot

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

i am getting following logs

mediashare

i am not getting that response to parse, remain is perfectly working. please help me in this issue i am nearly to complete this feature.

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);
    }        
},    `
  1. no need to import jxt, as required JXT object is passed to module as stanzas
  2. your mediaPushService is not a Promise, so you can not use then 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.

@weiss

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.