ForNeVeR / Kaiwa

A modern XMPP Web client
MIT License
74 stars 13 forks source link

XEP 0363: HTTP File Upload support #218

Closed patelvanita360 closed 6 years ago

patelvanita360 commented 6 years ago

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

patelvanita360 commented 6 years ago

Hello,

Is there any update on this issue ?

vitalyster commented 6 years ago

Do you have any logs in js console? (try to set localStorage.debug = true to get more verbose output)

patelvanita360 commented 6 years ago

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

patelvanita360 commented 6 years ago

Hello,

Is there any update on this issue ?

ForNeVeR commented 6 years ago

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?

vitalyster commented 6 years ago

@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

patelvanita360 commented 6 years ago

Can you please tel me how to put image on given slot pur URL?

On Thu, May 3, 2018, 12:05 PM vitalyster notifications@github.com wrote:

@patelvanita360 https://github.com/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

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ForNeVeR/Kaiwa/issues/218#issuecomment-386203012, or mute the thread https://github.com/notifications/unsubscribe-auth/Aje4zm8efrbudkGjVq69RfHLKLyXQoDJks5tuqU3gaJpZM4S5sO1 .

vitalyster commented 6 years ago

Just use Fetch API

patelvanita360 commented 6 years ago

Can you please share any reference?

On Thu, May 3, 2018, 2:39 PM vitalyster notifications@github.com wrote:

Just use Fetch API

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ForNeVeR/Kaiwa/issues/218#issuecomment-386234357, or mute the thread https://github.com/notifications/unsubscribe-auth/Aje4zgpW8BVc-oWh2UWLDqmv3Gbz5Dwrks5tuslggaJpZM4S5sO1 .

vitalyster commented 6 years ago

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

patelvanita360 commented 6 years ago

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.

vitalyster commented 6 years ago

You must enable CORS header on HTTP URLs on your server side to make HTTP requests from browser applications

patelvanita360 commented 6 years ago

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)

vitalyster commented 6 years ago

Sure, because it allows only HTTP PUT method

patelvanita360 commented 6 years ago

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

vitalyster commented 6 years ago

As your server supports CORS, you should NOT add no-cors parameter

patelvanita360 commented 6 years ago

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

vitalyster commented 6 years ago

Not sure I'm understand your question. What is "make it dynamic"?

patelvanita360 commented 6 years ago

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.

vitalyster commented 6 years ago

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
patelvanita360 commented 6 years ago

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

vitalyster commented 6 years ago

client.sendIQ have second callback parameter (error, response) where response is the reply to your slot request you passed as first parameter

patelvanita360 commented 6 years ago

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); }; };

vitalyster commented 6 years ago
client.mediaPushService = function (message) {
return client.sendIq(message);
};

I did not see you are using sendIq callback

patelvanita360 commented 6 years ago

i had use like this , but its not working

client.mediaPushService = function (message) { client.sendIq(message,function(error,response){ console.log(response); }); }

vitalyster commented 6 years ago

what is in log?

vitalyster commented 6 years ago

And I didn't see slot urls definition in your shareMedia stanza extension

patelvanita360 commented 6 years ago

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.

patelvanita360 commented 6 years ago

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);
    }        
},    `
vitalyster commented 6 years ago

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

patelvanita360 commented 6 years ago

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
vitalyster commented 6 years ago

413 (Request Entity Too Large)

That is your server configuration

patelvanita360 commented 6 years ago

i have set headers on server , can you suggest me which other configuration need to change ?

vitalyster commented 6 years ago

Entity too large means your upload limit too low

patelvanita360 commented 6 years ago

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?

vitalyster commented 6 years ago

Error message clearly states it does not found cors header on server

patelvanita360 commented 6 years ago

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: {}
vitalyster commented 6 years ago

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

patelvanita360 commented 6 years ago

https://docs.ejabberd.im/admin/configuration/#mod-http-upload i have setup custom headers from here is there any reference link to fix this ?

vitalyster commented 6 years ago

Nevermind, I forgot you are using ejabberd

vitalyster commented 6 years ago

I think you should configure your PUT url as put_url: "https://@HOST@/upload:5444" as module maybe adds headers only to docroot urls

vitalyster commented 6 years ago

You also miss host configuration which is upload.@HOST@ by default, and your PUT url may should match host url to set custom_headers

patelvanita360 commented 6 years ago

still got same error , is there any other method to upload image data ?

vitalyster commented 6 years ago

That is your server misconfiguration, ask ejabberd support on that

vitalyster commented 6 years ago

trying to ping @weiss :)

weiss commented 6 years ago

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.

vitalyster commented 6 years ago

@weiss I think he set upload limits and now only CORS headers are not configured

weiss commented 6 years ago

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.

patelvanita360 commented 6 years ago

@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

vitalyster commented 6 years ago

Response to preflight request

Preflight request is OPTIONS request, need to check is it really handled my mod_http_upload