Hey, how about making a chrome extension for the _W_ file sharing DApp
You could connect the extension to Evernode via its APIs or whatever. The extension would handle the interaction with the Evernode network, chunk files, upload them to a decentralized storage contract, and retrieve them when necessary.
You'd need a few extra files like a manifest.json but it would be amazing!
XRPLWin 's _W_ File Sharing Chrome Extension
//Chrome_contract.js XRPLWin 's _W_ File Sharing Chrome Extension
// Experimental
//Chrome_contract.js
const bson = require('bson');
const { filesharingdapp } = require('./filesharingdapp');
// Main function (replacing the HotPocket contract initiation)
async function contract() {
// Initialize the application logic.
const app = new filesharingdapp();
// Replace the HotPocket interaction with a manual trigger for testing the logic.
// We can simulate user inputs for testing.
// Simulated user input for testing purposes.
const simulatedUserInput = {
// Example input that would normally come from the user
command: "uploadFile",
data: {
filename: "example.txt",
content: "This is a test file."
}
};
// Instead of waiting for networked user inputs, simulate a "user" manually.
const simulatedUser = {
send: async (output) => {
console.log("Output to simulated user:", output);
}
};
// Simulate handling a read-only mode (just an example, modify based on your logic needs).
const isReadOnly = true;
// Pass the simulated user input to the app's handleRequest logic.
await app.handleRequest(simulatedUser, simulatedUserInput, isReadOnly);
}
// Manually invoke the contract function to simulate its behavior.
contract();
//Chrome_contract.js
const bson = require('bson');
const { filesharingdapp } = require('./filesharingdapp');
// Main function (replacing the HotPocket contract initiation)
async function contract() {
// Initialize the application logic.
const app = new filesharingdapp();
// Replace the HotPocket interaction with a manual trigger for testing the logic.
// We can simulate user inputs for testing.
// Simulated user input for testing purposes.
const simulatedUserInput = {
// Example input that would normally come from the user
command: "uploadFile",
data: {
filename: "example.txt",
content: "This is a test file."
}
};
// Instead of waiting for networked user inputs, simulate a "user" manually.
const simulatedUser = {
send: async (output) => {
console.log("Output to simulated user:", output);
}
};
// Simulate handling a read-only mode (just an example, modify based on your logic needs).
const isReadOnly = true;
// Pass the simulated user input to the app's handleRequest logic.
await app.handleRequest(simulatedUser, simulatedUserInput, isReadOnly);
}
// Manually invoke the contract function to simulate its behavior.
contract();
Last edited by Numerus on Tue Nov 19, 2024 4:00 am, edited 1 time in total.
//Chrome_filesharingdapp.js XRPLWin 's _W_ File Sharing Chrome Extension
// Experimental
//Chrome_filesharingdapp.js
const fs = require('fs');
const crypto = require('crypto');
const htmlFile = 'template/app.html';
const uploadDir = '../uploadedfiles';
// Keeping files in state (without HotPocket consensus)
export class filesharingdapp {
sendOutput; // This function will send output back to the user (manually for standalone version).
sendRawOutput; // This function will send raw output.
async handleRequest(user, message, isReadOnly) {
// Serve the HTML of this DApp
if (message.type == 'gethtml') {
await this.sendOutput(user, {
type: 'html',
data: (fs.readFileSync(htmlFile)).toString()
});
}
// Download metadata
else if (message.type == 'download-metadata') {
if (message.uploaderpubkey && message.fileid) {
const userID = message.uploaderpubkey;
const metafile = uploadDir + '/' + userID + '/' + parseInt(message.fileid) + '/metadata.json';
let metadataContents = (fs.readFileSync(metafile)).toString();
// Can add protection of properties here, as mentioned (e.g., remove "_" prefixed props)
await this.sendOutput(user, {
type: "json",
data: metadataContents
});
}
}
// File download request
else if (message.type == 'download') {
if (message.uploaderpubkey && message.fileid && message.part) {
const userID = message.uploaderpubkey;
const metafile = uploadDir + '/' + userID + '/' + parseInt(message.fileid) + '/metadata.json';
let metadataContents = JSON.parse((fs.readFileSync(metafile)).toString());
let allowDownload = true;
// If password protection is enabled, verify password
if (metadataContents.access == 'password') {
allowDownload = false;
if (message.password) {
let sentPasswordHash = crypto.createHash('md5').update(message.password).digest("hex");
if (sentPasswordHash == metadataContents._access_password) {
allowDownload = true;
}
}
}
if (allowDownload) {
const chunkfile = uploadDir + '/' + userID + '/' + parseInt(message.fileid) + '/' + parseInt(message.part) + '.chunk';
let chunkContents = (fs.readFileSync(chunkfile));
await this.sendOutput(user, {
type: "filepart",
data: chunkContents
});
} else {
await this.sendOutput(user, {
type: 'error',
error: 'Forbidden - password is invalid'
});
}
}
}
// Upload metadata
else if (message.type == 'upload-metadata') {
if (isReadOnly) {
await this.sendOutput(user, {
type: 'error',
error: 'Not supported in readonly mode'
});
} else if (message.fileid && message.contents) {
const userID = user.publicKey;
const dir = uploadDir + '/' + userID + '/' + parseInt(message.fileid);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(dir + '/metadata.json')) {
fs.writeFileSync(dir + '/metadata.json', message.contents);
}
await this.sendOutput(user, {
type: 'success',
message: 'Metadata file stored'
});
}
}
// Upload file chunk
else if (message.type == 'upload') {
if (isReadOnly) {
await this.sendOutput(user, {
type: 'error',
error: 'Not supported in readonly mode'
});
} else if (message.fileid && message.totalchunks && message.chunkno && message.chunk) {
const userID = user.publicKey;
const dir = uploadDir + '/' + userID + '/' + parseInt(message.fileid);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
let chunkFilepath = dir + '/' + parseInt(message.chunkno) + '.chunk';
if (!fs.existsSync(chunkFilepath)) {
fs.writeFileSync(chunkFilepath, Buffer.from(message.chunk, "hex"));
}
await this.sendRawOutput(user, {
type: 'success',
message: 'Chunk ' + parseInt(message.chunkno) + ' stored'
});
}
}
// Unknown message type
else {
await this.sendOutput(user, {
type: 'error',
error: 'Unknown message type'
});
}
}
}
//Chrome_filesharingdapp.js
const fs = require('fs');
const crypto = require('crypto');
const htmlFile = 'template/app.html';
const uploadDir = '../uploadedfiles';
// Keeping files in state (without HotPocket consensus)
export class filesharingdapp {
sendOutput; // This function will send output back to the user (manually for standalone version).
sendRawOutput; // This function will send raw output.
async handleRequest(user, message, isReadOnly) {
// Serve the HTML of this DApp
if (message.type == 'gethtml') {
await this.sendOutput(user, {
type: 'html',
data: (fs.readFileSync(htmlFile)).toString()
});
}
// Download metadata
else if (message.type == 'download-metadata') {
if (message.uploaderpubkey && message.fileid) {
const userID = message.uploaderpubkey;
const metafile = uploadDir + '/' + userID + '/' + parseInt(message.fileid) + '/metadata.json';
let metadataContents = (fs.readFileSync(metafile)).toString();
// Can add protection of properties here, as mentioned (e.g., remove "_" prefixed props)
await this.sendOutput(user, {
type: "json",
data: metadataContents
});
}
}
// File download request
else if (message.type == 'download') {
if (message.uploaderpubkey && message.fileid && message.part) {
const userID = message.uploaderpubkey;
const metafile = uploadDir + '/' + userID + '/' + parseInt(message.fileid) + '/metadata.json';
let metadataContents = JSON.parse((fs.readFileSync(metafile)).toString());
let allowDownload = true;
// If password protection is enabled, verify password
if (metadataContents.access == 'password') {
allowDownload = false;
if (message.password) {
let sentPasswordHash = crypto.createHash('md5').update(message.password).digest("hex");
if (sentPasswordHash == metadataContents._access_password) {
allowDownload = true;
}
}
}
if (allowDownload) {
const chunkfile = uploadDir + '/' + userID + '/' + parseInt(message.fileid) + '/' + parseInt(message.part) + '.chunk';
let chunkContents = (fs.readFileSync(chunkfile));
await this.sendOutput(user, {
type: "filepart",
data: chunkContents
});
} else {
await this.sendOutput(user, {
type: 'error',
error: 'Forbidden - password is invalid'
});
}
}
}
// Upload metadata
else if (message.type == 'upload-metadata') {
if (isReadOnly) {
await this.sendOutput(user, {
type: 'error',
error: 'Not supported in readonly mode'
});
} else if (message.fileid && message.contents) {
const userID = user.publicKey;
const dir = uploadDir + '/' + userID + '/' + parseInt(message.fileid);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(dir + '/metadata.json')) {
fs.writeFileSync(dir + '/metadata.json', message.contents);
}
await this.sendOutput(user, {
type: 'success',
message: 'Metadata file stored'
});
}
}
// Upload file chunk
else if (message.type == 'upload') {
if (isReadOnly) {
await this.sendOutput(user, {
type: 'error',
error: 'Not supported in readonly mode'
});
} else if (message.fileid && message.totalchunks && message.chunkno && message.chunk) {
const userID = user.publicKey;
const dir = uploadDir + '/' + userID + '/' + parseInt(message.fileid);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
let chunkFilepath = dir + '/' + parseInt(message.chunkno) + '.chunk';
if (!fs.existsSync(chunkFilepath)) {
fs.writeFileSync(chunkFilepath, Buffer.from(message.chunk, "hex"));
}
await this.sendRawOutput(user, {
type: 'success',
message: 'Chunk ' + parseInt(message.chunkno) + ' stored'
});
}
}
// Unknown message type
else {
await this.sendOutput(user, {
type: 'error',
error: 'Unknown message type'
});
}
}
}
Last edited by Numerus on Tue Nov 19, 2024 4:01 am, edited 1 time in total.
Re: XRPLWin 's _W_ File Sharing Chrome Extension
// Experimental
Chrome-Manifest.json
{
"manifest_version": 3,
"name": "Evernode File Sharing DApp",
"version": "1.0",
"description": "A decentralized file-sharing DApp powered by EVR and Evernode.",
"icons": {
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"action": {
"default_popup": "index.html",
"default_icon": "icons/icon48.png"
},
"permissions": [
"storage",
"activeTab",
"scripting",
"wallet"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["app.js"]
}
],
"web_accessible_resources": [
{
"resources": ["index.html", "app.js", "wallet.js"],
"matches": ["<all_urls>"]
}
]
}
Chrome-Manifest.json
{
"manifest_version": 3,
"name": "Evernode File Sharing DApp",
"version": "1.0",
"description": "A decentralized file-sharing DApp powered by EVR and Evernode.",
"icons": {
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"action": {
"default_popup": "index.html",
"default_icon": "icons/icon48.png"
},
"permissions": [
"storage",
"activeTab",
"scripting",
"wallet"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["app.js"]
}
],
"web_accessible_resources": [
{
"resources": ["index.html", "app.js", "wallet.js"],
"matches": ["<all_urls>"]
}
]
}
Last edited by Numerus on Tue Nov 19, 2024 4:02 am, edited 1 time in total.
// Chrome_app.js - XRPLWin 's _W_ File Sharing Chrome Extension
// Experimental
// Chrome_app.js
<%--
Steps:
Modify the Contract: The contract should generate a unique File ID (e.g., based on a hash or incremental ID) once the file is uploaded.
Update the Upload Logic: After a successful file upload, the app should request the generated File ID from the contract and display it to the user.
Remove Manual File ID Input: Since the File ID will now be generated automatically, users don’t need to manually enter it.
--%>
// Handle file uploads and request a generated File ID from the contract
async function uploadFile() {
const fileInput = document.getElementById('fileInput').files[0];
if (!fileInput) {
alert("Please select a file to upload.");
return;
}
const formData = new FormData();
formData.append('file', fileInput);
try {
// Simulate the file upload to the contract or server
const uploadResponse = await fetch('/upload', { method: 'POST', body: formData });
const uploadResult = await uploadResponse.json();
// Assuming the contract returns a generated File ID
const generatedFileId = uploadResult.fileId;
console.log("File uploaded successfully. Generated File ID:", generatedFileId);
// Display the generated File ID to the user
document.getElementById('generatedFileId').innerText = `File ID: ${generatedFileId}`;
} catch (error) {
console.error("File upload failed:", error);
alert("Error uploading file. Please try again.");
}
}
// Handle file downloads using the File ID generated from the contract
async function downloadFile() {
const fileId = document.getElementById('fileId').value;
if (!fileId) {
alert("Please enter a valid File ID.");
return;
}
try {
const downloadResponse = await fetch(`/download?fileId=${fileId}`);
const fileBlob = await downloadResponse.blob();
const downloadUrl = URL.createObjectURL(fileBlob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = 'downloaded_file';
link.click();
} catch (error) {
console.error("File download failed:", error);
alert("Error downloading file. Please try again.");
}
}
// Chrome_app.js
<%--
Steps:
Modify the Contract: The contract should generate a unique File ID (e.g., based on a hash or incremental ID) once the file is uploaded.
Update the Upload Logic: After a successful file upload, the app should request the generated File ID from the contract and display it to the user.
Remove Manual File ID Input: Since the File ID will now be generated automatically, users don’t need to manually enter it.
--%>
// Handle file uploads and request a generated File ID from the contract
async function uploadFile() {
const fileInput = document.getElementById('fileInput').files[0];
if (!fileInput) {
alert("Please select a file to upload.");
return;
}
const formData = new FormData();
formData.append('file', fileInput);
try {
// Simulate the file upload to the contract or server
const uploadResponse = await fetch('/upload', { method: 'POST', body: formData });
const uploadResult = await uploadResponse.json();
// Assuming the contract returns a generated File ID
const generatedFileId = uploadResult.fileId;
console.log("File uploaded successfully. Generated File ID:", generatedFileId);
// Display the generated File ID to the user
document.getElementById('generatedFileId').innerText = `File ID: ${generatedFileId}`;
} catch (error) {
console.error("File upload failed:", error);
alert("Error uploading file. Please try again.");
}
}
// Handle file downloads using the File ID generated from the contract
async function downloadFile() {
const fileId = document.getElementById('fileId').value;
if (!fileId) {
alert("Please enter a valid File ID.");
return;
}
try {
const downloadResponse = await fetch(`/download?fileId=${fileId}`);
const fileBlob = await downloadResponse.blob();
const downloadUrl = URL.createObjectURL(fileBlob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = 'downloaded_file';
link.click();
} catch (error) {
console.error("File download failed:", error);
alert("Error downloading file. Please try again.");
}
}