Javascript paste event - getting file name - javascript

The paste event files come back with a generic name such as 'image.png' (inside file.name on the File object and items.getAsFile().name) and that isn't super useful for me. I wrote the following code to get the name of the file I've pasted but it seems dirty and a bit of a hack, is there a better way to do this ?
Codepen example:
https://codepen.io/designalchemy/pen/mddvbyV?editors=1010
How it works :
get items (DataTransferList) and files (FileList) from paste event
use .getAsString() method from the first item in the DataTransferList to get the file name
as this method destroys the .getAsFile method use the File from files object
rename file by cloning and applying the name
My code:
window.addEventListener('paste', onPasteEvent)
const onPasteEvent = async e => {
stopDefaults(e)
const { items, files } = e.clipboardData
// the .getAsString destorys the file object on 'items'
// also can only do one paste at a time, no multi
const file = files[0]
const fileType = file.type
if (file && fileType.includes('image')) {
const fileName = await new Promise(res => {
items[0].getAsString(e => {
res(e)
})
})
// clone file so we can change the name
const newFile = [new File([file], fileName, { type: fileType })]
if (newFile && newFile.length > 0) {
callback && callback(newFile)
}
}
}

My solution: (although I am not sure if it fits your need)
I use FormData …
('say' is my private 'prettyprint',and code is Coffescript)
current_file=evt.originalEvent.clipboardData.files[0]
say "bad bad!",current_file
formData= new FormData
formData.append("myfile",current_file,"my name")
current_file=formData.get("myfile")
say "tada!", current_file
output:
'bad bad!', File(name:'image.png' size:16203 type:'image/png')
'tada!', File(name:'my name' size:16203 type:'image/png')
This is fast and synchronous.
I hope this helps and is self explaining.
If not tell me.

Related

Electron - write file before open save dialog

I'm using electron to develop an app. after some encryption operations are done, I need to show a dialog to the user to save the file. The filename I want to give to the file is a random hash but I have no success also with this. I'm trying with this code but the file will not be saved. How I can fix this?
const downloadPath = app.getPath('downloads')
ipcMain.on('encryptFiles', (event, data) => {
let output = [];
const password = data.password;
data.files.forEach( (file) => {
const buffer = fs.readFileSync(file.path);
const dataURI = dauria.getBase64DataURI(buffer, file.type);
const encrypted = CryptoJS.AES.encrypt(dataURI, password).toString();
output.push(encrypted);
})
const filename = hash.createHash('md5').toString('hex');
console.log(filename)
const response = output.join(' :: ');
dialog.showSaveDialog({title: 'Save encrypted file', defaultPath: downloadPath }, () => {
fs.writeFile(`${filename}.mfs`, response, (err) => console.log(err) )
})
})
The problem you're experiencing is resulting from the asynchronous nature of Electron's UI functions: They do not take callback functions, but return promises instead. Thus, you do not have to pass in a callback function, but rather handle the promise's resolution. Note that this only applies to Electron >= version 6. If you however run an older version of Electron, your code would be correct -- but then you should really update to a newer version (Electron v6 was released well over a year ago).
Adapting your code like below can be a starting point to solve your problem. However, since you do not state how you generate the hash (where does hash.createHash come from?; did you forget to declare/import hash?; did you forget to pass any message string?; are you using hash as an alias for NodeJS' crypto module?), it is (at this time) impossible to debug why you do not get any output from console.log (filename) (I assume you mean this by "in the code, the random filename will not be created"). Once you provide more details on this problem, I'd be happy to update this answer accordingly.
As for the default filename: As per the Electron documentation, you can pass a file path into dialog.showSaveDialog () to provide the user with a default filename.
The file type extension you're using should also actually be passed with the file extension into the save dialog. Also passing this file extension as a filter into the dialog will prevent users from selecting any other file type, which is ultimately what you're also currently doing by appending it to the filename.
Also, you could utilise CryptoJS for the filename generation: Given some arbitrary string, which could really be random bytes, you could do: filename = CryptoJS.MD5 ('some text here') + '.mfs'; However, remember to choose the input string wisely. MD5 has been broken and should thus no longer be used to store secrets -- using any known information which is crucial for the encryption of the files you're storing (such as data.password) is inherently insecure. There are some good examples on how to create random strings in JavaScript around the internet, along with this answer here on SO.
Taking all these issues into account, one might end up with the following code:
const downloadPath = app.getPath('downloads'),
path = require('path');
ipcMain.on('encryptFiles', (event, data) => {
let output = [];
const password = data.password;
data.files.forEach((file) => {
const buffer = fs.readFileSync(file.path);
const dataURI = dauria.getBase64DataURI(buffer, file.type);
const encrypted = CryptoJS.AES.encrypt(dataURI, password).toString();
output.push(encrypted);
})
// not working:
// const filename = hash.createHash('md5').toString('hex') + '.mfs';
// alternative requiring more research on your end
const filename = CryptoJS.MD5('replace me with some random bytes') + '.mfs';
console.log(filename);
const response = output.join(' :: ');
dialog.showSaveDialog(
{
title: 'Save encrypted file',
defaultPath: path.format ({ dir: downloadPath, base: filename }), // construct a proper path
filters: [{ name: 'Encrypted File (*.mfs)', extensions: ['mfs'] }] // filter the possible files
}
).then ((result) => {
if (result.canceled) return; // discard the result altogether; user has clicked "cancel"
else {
var filePath = result.filePath;
if (!filePath.endsWith('.mfs')) {
// This is an additional safety check which should not actually trigger.
// However, generally appending a file extension to a filename is not a
// good idea, as they would be (possibly) doubled without this check.
filePath += '.mfs';
}
fs.writeFile(filePath, response, (err) => console.log(err) )
}
}).catch ((err) => {
console.log (err);
});
})

How to check if path is file or directory

Is there any clean way to check if a path is a file or directory?
Currently I am using this:
exports.isDirectory = (dirPath) => {
return fs.lstatSync(dirPath).isDirectory()
}
But my problem here is that if dirPath does not exist yet, then lstatSync gives out an error.
Then I also tried:
exports.getFileName = (filePath) => {
return filePath.split(/[\\\/]/).pop()
}
exports.isDirectory = (dirPath) => {
return exports.getFileName(dirPath) === ''
}
And call:
const home = require('os').homedir()
const sampleLoc = path.join(home, '/.folder/another'))
isDirectory(sampleLoc)
But it only basically thinks another is the filename and returns false on isDirectory.
Can't really check for the presence of . (like an extension of a file name) since my folders can have dots anywhere on its name.
How can I check if the given path is for a file or directory? (considering it does not exist yet at the point of checking)?
Help!

How to properly read all file types with react js and add to IPFS in browser

enter image description here
I am trying to read a file (which can be anything like video, image , text ) with react .
I want to add data in the IPFS through Node JS. In normal case(which means when i m only writing in js) I would do it like this :
const data = await fs.readFile(file_path)
console.log(data)
const filesAdded = await nodeDialer.add({
path:file_path,
content:data,
})
console.log(filesAdded)
THe files would be added easily(any type of file).
I tried fs and found out it only works in the node side not on the react side.
and whereever i looked they were using some readastext functions for .txt file or .csv file.
So a proper solution, I couldn't find.
I solved it with this:
Created a event handler for file upload :
captureFile(event){
const file = event.target.files[0]
this.setState({ file_path : file})
console.log('--------------', this.state.file_path)
const reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {
this.setState({ buffer: Buffer(reader.result) })
console.log('buffer --- ', this.state.buffer)
console.log('path --- ', this.state.file_path.name)
}
}
then I add the file in IPFS with :
const filesAdded = await node.add({
path: this.state.file_path.name ,
content:this.state.buffer
})

FS file watcher, get changes

I want to implement a file system watcher using node.js so that it watches a particular JSON file for changes.
And then, i would like to get what changed inside the file.
Here's one way:
Load the current file contents and parse it to an object, keeping it in-memory.
Watch for file changes, using fs.watch.
On change, load the new file contents as an object.
Perform an object diff between the current object and new object; e.g using diff.
Set current object as new object.
Repeat on change.
Here's an example:
const fs = require('fs')
const diff = require('deep-diff')
const filepath = './foo.json'
const getCurrent = () => JSON.parse(fs.readFileSync(filepath, {
encoding: 'utf8'
}))
let currObj = getCurrent()
fs.watch(filepath, { encoding: 'buffer' }, (eventType, filename) => {
if (eventType !== 'change') return
const newObj = getCurrent()
const differences = diff(currObj, newObj)
console.log(differences)
// { kind: 'N' } for new key additions
// { kind: 'E' } for edits
// { kind: 'D' } for deletions
currObj = newObj
})
Note that I'm using fs.readFileSync here for brevity. You should be better off using fs.readFile instead which is non-blocking.

file input files not read onChange on mobile

I'm building a puzzle app in React that allows the user to upload their own puzzles. This works fine on the web (the user clicks the input's label and it opens a dialog. When the user picks a file the onChange event is triggered), but on mobile, or at least on Chrome on Android, the files are not read...
This is where the input is declared:
<div className="file-input-wrapper">
<label for="puzzleUpload" className="button-dark">Upload Puzzle(s)</label>
<input type="file"
accept="application/json"
multiple
id="puzzleUpload"
onChange={handleFiles}/>
</div>
and this is the handleFiles() method
// when a file is uploaded, this checks to see that it's the right type, then adds it to the puzzle list
const handleFiles = () => {
var selectedFiles = document.getElementById('puzzleUpload').files;
// checks if the JSON is a valid puzzle
const validPuzzle = (puzzle) => {
let keys = ["name", "entitySetID", "logic", "size"];
return keys.every((key) => {return puzzle.hasOwnProperty(key)});
};
const onLoad = (event) => {
let puzzle = JSON.parse(event.target.result);
if(validPuzzle(puzzle)) {
appendPuzzleList(puzzle);
}
else {
console.log("JSON file does not contain a properly formatted Logike puzzle")
}
};
//checks the file type before attempting to read it
for (let i = 0; i < selectedFiles.length; i++) {
if(selectedFiles[i].type === 'application/json') {
//creates new readers so that it can read many files sequentially.
var reader = new FileReader();
reader.onload = onLoad;
reader.readAsText(selectedFiles[i]);
}
}
};
A working prototype with the most recent code can be found at http://logike.confusedretriever.com and it's possible to quickly write compatible JSON using the builder in the app.
I've been looking up solutions for the past hour and a half and have come up empty handed, so any help would be greatly appreciated! I read the FileReader docs, and everything seems to be supported, so I'm kind of stumped.
Interestingly, the file IS selected (you can see the filename in the ugly default version of the input once it's selected, but I hide it via CSS), so I'm tempted to implement a mobile-only button to trigger the event, if there isn't a more legit solution...
Chrome uses the OS's list of known MIME Types.
I guess Android doesn't know about "application/json", and at least, doesn't map the .json extension to this MIME type, this means that when you upload your File in this browser, you won't have the correct type property set, instead, it is set to the empty string ("").
But anyway, you shouldn't trust this type property, ever.
So you could always avoid some generic types, like image/*, video/*, but the only reliable way to know if it was a valid JSON file or not will be by actually reading the data contained in your file.
But I understand you don't want to start this operation if your user provides a huge file, like a video.
One simple solution might be to check the size property instead, if you know in which range your generated files might come.
One less simple but not so hard either solution would be to prepend a magic number (a.k.a File Signature)to your generated files (if your app is the only way to handle these files).
Then you would just have to check this magic number only before going to read the whole file:
// some magic-number (here "•MJS")
const MAGIC_NB = new Uint8Array([226, 128, 162, 77, 74, 83]);
// creates a json-like File, with our magic_nb prepended
function generateFile(data) {
const str = JSON.stringify(data);
const blob = new Blob([MAGIC_NB, str], {
type: 'application/myjson' // won't be used anyway
});
return new File([blob], 'my_file.json');
}
// checks whether the provided blob starts with our magic numbers or not
function checkFile(blob) {
return new Promise((res, rej) => {
const reader = new FileReader();
reader.onload = e => {
const arr = new Uint8Array(reader.result);
res(!arr.some((v, i) => MAGIC_NB[i] !== v));
};
reader.onerror = rej;
// read only the length of our magic nb
reader.readAsArrayBuffer(blob.slice(0, MAGIC_NB.length));
});
}
function handleFile(file) {
return checkFile(file).then(isValid => {
if (isValid) {
return readFile(file);
} else {
throw new Error('invalid file');
}
});
}
function readFile(file) {
return new Promise((res, rej) => {
const reader = new FileReader();
reader.onload = e => res(JSON.parse(reader.result));
reader.onerror = rej;
// don't read the magic_nb part again
reader.readAsText(file.slice(MAGIC_NB.length));
});
}
const my_file = generateFile({
key: 'value'
});
handleFile(my_file)
.then(obj => console.log(obj))
.catch(console.error);
And in the same way note that all browsers won't accept all the schemes for the accept attribute, and that you might want to double your MIME notation with a simple extension one (anyway even MIMEs are checked only against this extension).

Categories