Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable To Pass Objects/Arrays in IPCRenderer, An object could not be cloned EventEmitter.i.send.i.send

I am unable to pass any object or arrays to IPCRenderer.

I am getting error when passing an object or array through ipcs, I have even tried to send by converting to string using JSON.stringify but it converts it into empty object string.

I have tried passing a fileList, an array of object & even an object nothing passes. only string or handwritten objects are working.

I've read that it uses Structured Clone Algorithm and fileList & Array is allowed by this algorithm

ERROR:

electron/js2c/renderer_init.js:74 Uncaught Error: An object could not be cloned.
    at EventEmitter.i.send.i.send (electron/js2c/renderer_init.js:74)
    at HTMLButtonElement.compressNow (ImageHandling.js:190)

ElectronJS ipcRenderer Error An object could not be cloned.

I have tried many possible solutions but nothing worked

code:


const compressNow = () => {
    ipcRenderer.send("image:compress", filess).  ///This is the error.
    // filess is a variable containing an array of selected files from an HTML input.
}

Now i have tried to send filess as JSON.stringify, i tried to send it as an object but nothing works unless i manually write a dummy object or string.

Here's My Github Repo for this project

Files With ErrorJ:-

ImageHandling.js

const fs = window.require('fs');
const {ipcRenderer} = require("electron")
const SELECT = (target) => document.querySelector(`${target}`)
var filess = []

const imgUploadInput = SELECT("#imgUploadInput")
const warning = SELECT("#warning")

const setImgBase64 = (imgEl, file) => {

    const ReadAbleFile = fs.readFileSync(file.path).toString('base64')
    let src = "data:image/png;base64," + ReadAbleFile

    imgEl.setAttribute("src", src)
    // El.src=src

    // console.log(`FIXED IMAGE # ${imgEl} `,ReadAbleFile)

}
const renderImages = () => {
    const files = filess && Array.from(filess)
    const defaultImg = SELECT("#defaultImg")
    const addImgBtn = SELECT("#addImgBtn")
    imgUploadInput.disabled = true;

    let numOfFiles = files.length

    if (numOfFiles < 1) {
        SELECT("#compressContainer").style.visibility = "hidden"
    } else {
        SELECT("#compressContainer").style.visibility = "visible"
    }
    if (numOfFiles > 49) {
        warning.innerHTML = `<b style="font-weight:bold; color:red;">WARNING:</b><br/> 
                               <span style="padding:10px;text-align:left">
                               Your processor/computer may not be able to process ${numOfFiles} Images at once, We recommend selecting less than 50 Images at once for better performance.
                                </span>
                                `;
    }
    addImgBtn.innerHTML = `LOADING.....`
    if (defaultImg && numOfFiles > 0) 
        defaultImg.remove();
    


    setTimeout(() => {

        if (files && numOfFiles > 0) {
            let displayImages = SELECT("#displayImages")
            displayImages.innerHTML = ""
            files ?. forEach((file, i) => {
                let divEl = document.createElement("div")
                let imgEl = document.createElement("img")
                imgEl.src = file.path

                imgEl.id = `PNG_${i}_${
                    btoa(file.name)
                }`
                divEl.className = "displayedImg"

                imgEl.setAttribute("onclick", `document.getElementById('ImageView').src=this.src`)


                const a = document.createElement("a")
                a.appendChild(imgEl)

                a.setAttribute("href", `#ViewImage`)
                a.className = "perfundo__link"


                divEl.appendChild(a)

                divEl.className = "displayedImg perfundo"

                displayImages.appendChild(divEl)


                if (i == files.length - 1) {
                    warning.innerHTML = "";
                    updateNumOfImages();
                }
                imgEl.onerror = () => setImgBase64(imgEl, file) // converting to base64 only on error, this make performance better and help us avoid freezes. (before this i was converting all images to base64 wither errored or not that was making computer freez)
            })
            addImgBtn.innerHTML = "+ Add MORE"
            imgUploadInput.disabled = false
            findDuplicate()
        }

    }, 0);
}

const hasDuplicate=()=>{
    let FileNames = [... filess.map(f => f.name)]
    let duplicateFiles = filess.filter((file, i) => FileNames.indexOf(file.name) !== i)

    return {FileNames,duplicateFiles,FilesLength:duplicateFiles.length}
}
const findDuplicate = (forceAlert = false) => {
    if (filess && filess.length) {
        let {FileNames} = hasDuplicate()
        let {duplicateFiles} = hasDuplicate()
        if (duplicateFiles.length) { // alert(``)

            let countFiles = duplicateFiles.length
            let fileStr = countFiles > 1 ? "files" : "file"
            console.log("result from removeDup=> ", filess, " \n dupfilename=> ", FileNames, " \n dupfiles=> ", duplicateFiles)

            let shouldNotAsk = localStorage.getItem("NeverAsk")
            let msg = `You've selected ${
                countFiles > 1 ? countFiles : "a"
            } duplicate ${fileStr}`
            let duplInner = `<span style='color:red'> 
                               <b>WARNING</b>
                               <p style="margin:0px;line-height:1">  ${msg} .  <button onClick="findDuplicate(true)" type="button"  class="btn btn-danger btn-rounded  btn-sm">REMOVE DUPLICATE</button></p>
                              </span>`
            if (! shouldNotAsk || forceAlert) {
                swal("DUPLICATE FILES DETECTED", `${msg} , Would you like to un-select duplicate ${fileStr} having same name?`, {
                    icon: 'warning',
                    dangerMode: true,
                    buttons: {
                        cancel: true,
                        ...forceAlert ? {} : {
                            never: "Never Ask"
                        },
                        confirm: "Yes !"
                    }
                }).then((Yes) => {
                    if (Yes == "never") {
                        localStorage.setItem("NeverAsk", true)
                        warning.innerHTML=duplInner

                    } else if (Yes) {
                        removeDuplicates()

                    }
                })
            } else {
                warning.innerHTML=duplInner
            }
        }

    }
}


const removeDuplicates = (showAlert=true) => {
    
    let {FileNames} = hasDuplicate()
    let {duplicateFiles} = hasDuplicate()
    let duplicateFileNames = duplicateFiles.map(f => f.name)
    let uniqueFiles = filess.filter((file) => ! duplicateFileNames.includes(file.name))
    filess = [
        ... uniqueFiles,
        ... duplicateFiles
    ]

    console.log("result from removeDup=> ", filess, " \n filename=> ", FileNames, " \n dupfiles=> ", duplicateFiles, "\n unique fil=> ", uniqueFiles)
    renderImages()
    if(showAlert){
    swal("DONE", "Removed Duplicate Files ", {icon: 'success'}).then(() =>{ 
        renderImages()
        setTimeout(() => {
             let hasDuplicateFiles = hasDuplicate().FilesLength
             if(hasDuplicate){//Re-check if any duplicate files left after the current removal process. 
                 removeDuplicates(false) //Re-run the function to remove remaining. false will make sure that this alert does not show and the loop does not continue.
             }
             renderImages()

        }, 10);
    
    })
   }
}




const updateNumOfImages = () => {
    warning.innerHTML = `
                <span style="text-align:left; color:green">
                        Selected ${
        filess.length
    } Image(s)
                 </span>
                 `;
}


const compressNow = () => {
    ipcRenderer.send("image:compress", filess)
    // alert("WOW")
}


CompressBtn.addEventListener("click", compressNow)

imgUploadInput.addEventListener("change", (e) => {
    let SelectedFiles = e.target.files

    if (SelectedFiles && SelectedFiles.length) {
        filess = [
            ... filess,
            ... SelectedFiles
        ]
        renderImages()
    }
})
// SELECT("#imgUploadInput").addEventListener("drop",(e)=>console.log("DROP=> ",e))

UPDATE:-

I REPLACED THIS:

const compressNow = () => {

        ipcRenderer.send("image:compress",filess)
    
}

INTO THIS:-

const compressNow = () => {

    filess.forEach(file => {
        ipcRenderer.send("image:compress",file.path )
    });
}

Now here i am sending the files one by one via forEach, actually its sending string "file path" so thats how its working i am still confused why do i have to do this? why can't i send whole fileList i assume that this loop method is a bad practice because it will consume more CPU its one additional loop however it won't be necessary if i am able to send the whole array.

like image 881
Kashan Haider Avatar asked Jul 24 '20 21:07

Kashan Haider


4 Answers

See Behavior Changed: Sending non-JS objects over IPC now throws an exception. DOM objects etc. are not serializable. Electron 9.0 (and newer) throws "object could not be cloned" error when unserializable objects are sent.

In your code, File and FileList are DOM objects.

If you want to avoid using forEach, try this code:

const compressNow = () => {
    const paths = filess.map(f => f.path);
    ipcRenderer.send("image:compress", paths);
}
like image 140
noobar Avatar answered Nov 15 '22 07:11

noobar


Instead of sending the images save them in fs and send the path

like image 23
user2491530 Avatar answered Sep 27 '22 18:09

user2491530


Can refer to electron github issue tracker for this issue (already closed)

Error: An object could not be cloned #26338

Docs for ipcRenderer.send(channel, ...args)

This issue mainly comes when we have non-cloneable values like function within an object in data we are sending via IPC, to avoid that we can use JSON.stringify() before sending and JSON.parse() later on receiving end, but doing so will cause to lose some of the values eg:

const obj = {
 x :10,
 foo : ()=>{
    console.log('This is non-cloneable value')
 }
}

console.log(JSON.stringify(obj))

output:{"x":10}

like image 2
WasitShafi Avatar answered Nov 15 '22 07:11

WasitShafi


Instead of sending the images save them in fs and send the path

like image 1
Lingyong Sun Avatar answered Nov 15 '22 08:11

Lingyong Sun