Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
383 lines (310 sloc) 10.4 KB
/* global location */
'use strict'
const IPFS = require('ipfs')
const all = require('it-all')
const { Buffer } = IPFS
// Node
const $nodeId = document.querySelector('.node-id')
const $nodeAddresses = document.querySelector('.node-addresses')
const $logs = document.querySelector('#logs')
// Peers
const $peers = document.querySelector('#peers')
const $peersList = $peers.querySelector('#connected-peers')
const $workspacePeersList = $peers.querySelector('#workspace-peers')
const $multiaddrInput = document.querySelector('#multiaddr-input')
const $connectButton = document.querySelector('#peer-btn')
// Files
const $cidInput = document.querySelector('#cid-input')
const $fetchButton = document.querySelector('#fetch-btn')
const $dragContainer = document.querySelector('#drag-container')
const $progressBar = document.querySelector('#progress-bar')
const $fileHistory = document.querySelector('#file-history tbody')
const $emptyRow = document.querySelector('.empty-row')
// Misc
const $allDisabledButtons = document.querySelectorAll('button:disabled')
const $allDisabledInputs = document.querySelectorAll('input:disabled')
const $allDisabledElements = document.querySelectorAll('.disabled')
// Workspace inputs
const $workspaceInput = document.querySelector('#workspace-input')
const $workspaceBtn = document.querySelector('#workspace-btn')
let FILES = []
let workspace = (location.hash || 'default-workspace').replace(/^#/, '')
let fileSize = 0
let node
let info
/* ===========================================================================
Start the IPFS node
=========================================================================== */
async function start () {
if (!node) {
node = await IPFS.create({
repo: 'ipfs-' + Math.random(),
config: {
Addresses: {
Swarm: [
// This is a public webrtc-star server
'/dns4/wrtc-star1.par.dwebops.pub/tcp/443/wss/p2p-webrtc-star',
'/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star',
'/ip4/127.0.0.1/tcp/13579/wss/p2p-webrtc-star'
]
},
// If you want to connect to the public bootstrap nodes, remove the next line
Bootstrap: []
}
})
try {
info = await node.id()
const addressesHtml = info.addresses.map((address) => {
return `<li><pre>${address}</pre></li>`
}).join('')
$nodeId.innerText = info.id
$nodeAddresses.innerHTML = addressesHtml
$allDisabledButtons.forEach(b => { b.disabled = false })
$allDisabledInputs.forEach(b => { b.disabled = false })
$allDisabledElements.forEach(el => { el.classList.remove('disabled') })
} catch (err) {
return onError(err)
}
onSuccess('Node is ready.')
setInterval(async () => {
try {
await refreshPeerList()
} catch (err) {
err.message = `Failed to refresh the peer list: ${err.message}`
onError(err)
}
}, 1000)
setInterval(async () => {
try {
await refreshWorkspacePeerList()
} catch (err) {
err.message = `Failed to refresh the workspace peer list: ${err.message}`
onError(err)
}
}, 1000)
setInterval(async () => {
try {
await sendFileList()
} catch (err) {
err.message = `Failed to publish the file list: ${err.message}`
onError(err)
}
}, 10000)
try {
await subscribeToWorkpsace()
} catch (err) {
err.message = `Failed to subscribe to the workspace: ${err.message}`
return onError(err)
}
window.addEventListener('hashchange', async () => {
try {
await workspaceUpdated()
} catch (err) {
err.message = `Failed to subscribe to the updated workspace: ${err.message}`
onError(err)
}
})
}
}
/* ===========================================================================
Pubsub
=========================================================================== */
const messageHandler = (message) => {
const myNode = info.id.toString()
const hash = message.data.toString()
const messageSender = message.from
// append new files when someone uploads them
if (myNode !== messageSender && !FILES.includes(hash)) {
$cidInput.value = hash
getFile()
}
}
const subscribeToWorkpsace = async () => {
await node.pubsub.subscribe(workspace, messageHandler)
const msg = `Subscribed to workspace '${workspace}'`
$logs.innerHTML = msg
}
// unsubscribe from old workspace and re-subscribe to new one
const workspaceUpdated = async () => {
await node.pubsub.unsubscribe(workspace)
// clear files from old workspace
FILES = []
$fileHistory.innerHTML = ''
workspace = location.hash.replace(/^#/, '')
await subscribeToWorkpsace()
}
const publishHash = (hash) => {
const data = Buffer.from(hash)
return node.pubsub.publish(workspace, data)
}
/* ===========================================================================
Files handling
=========================================================================== */
const sendFileList = () => Promise.all(FILES.map(publishHash))
const updateProgress = (bytesLoaded) => {
let percent = 100 - ((bytesLoaded / fileSize) * 100)
$progressBar.style.transform = `translateX(${-percent}%)`
}
const resetProgress = () => {
$progressBar.style.transform = 'translateX(-100%)'
}
function appendFile (name, hash, size, data) {
const file = new window.Blob([data], { type: 'application/octet-binary' })
const url = window.URL.createObjectURL(file)
const row = document.createElement('tr')
const nameCell = document.createElement('td')
nameCell.innerHTML = name
const hashCell = document.createElement('td')
hashCell.innerHTML = hash
const sizeCell = document.createElement('td')
sizeCell.innerText = size
const downloadCell = document.createElement('td')
const link = document.createElement('a')
link.setAttribute('href', url)
link.setAttribute('download', name)
link.innerHTML = '<img width=20 class="table-action" src="assets/download.svg" alt="Download" />'
downloadCell.appendChild(link)
row.appendChild(nameCell)
row.appendChild(hashCell)
row.appendChild(sizeCell)
row.appendChild(downloadCell)
$fileHistory.insertBefore(row, $fileHistory.firstChild)
return publishHash(hash)
}
async function getFile () {
const hash = $cidInput.value
$cidInput.value = ''
if (!hash) {
return onError('No CID was inserted.')
} else if (FILES.includes(hash)) {
return onSuccess('The file is already in the current workspace.')
}
FILES.push(hash)
for await (const file of node.get(hash)) {
if (file.content) {
const content = Buffer.concat(await all(file.content))
await appendFile(file.name, hash, file.size, content)
onSuccess(`The ${file.name} file was added.`)
$emptyRow.style.display = 'none'
}
}
}
/* Drag & Drop
=========================================================================== */
const onDragEnter = () => $dragContainer.classList.add('dragging')
const onDragLeave = () => $dragContainer.classList.remove('dragging')
async function onDrop (event) {
onDragLeave()
event.preventDefault()
const files = Array.from(event.dataTransfer.files)
for (const file of files) {
fileSize = file.size // Note: fileSize is used by updateProgress
const fileAdded = await node.add({
path: file.name,
content: file
}, {
wrapWithDirectory: true,
progress: updateProgress
})
// As we are wrapping the content we use that hash to keep
// the original file name when adding it to the table
$cidInput.value = fileAdded.cid.toString()
resetProgress()
await getFile()
}
}
/* ===========================================================================
Peers handling
=========================================================================== */
async function connectToPeer (event) {
const multiaddr = $multiaddrInput.value
if (!multiaddr) {
throw new Error('No multiaddr was inserted.')
}
await node.swarm.connect(multiaddr)
onSuccess(`Successfully connected to peer.`)
$multiaddrInput.value = ''
}
async function refreshPeerList () {
const peers = await node.swarm.peers()
const peersAsHtml = peers.reverse()
.map((peer) => {
if (peer.addr) {
const addr = peer.addr.toString()
if (addr.indexOf('/p2p/') >= 0) {
return addr
} else {
return addr + peer.peer
}
}
})
.map((addr) => {
return `<tr><td>${addr}</td></tr>`
}).join('')
$peersList.innerHTML = peersAsHtml
}
async function refreshWorkspacePeerList () {
const peers = await node.pubsub.peers(workspace)
const peersAsHtml = peers.reverse()
.map((addr) => {
return `<tr><td>${addr}</td></tr>`
}).join('')
$workspacePeersList.innerHTML = peersAsHtml
}
/* ===========================================================================
Error handling
=========================================================================== */
function onSuccess (msg) {
$logs.classList.add('success')
$logs.innerHTML = msg
}
function onError (err) {
console.log(err)
let msg = 'An error occured, check the dev console'
if (err.stack !== undefined) {
msg = err.stack
} else if (typeof err === 'string') {
msg = err
}
$logs.classList.remove('success')
$logs.innerHTML = msg
}
window.onerror = onError
/* ===========================================================================
Boot the app
=========================================================================== */
const startApplication = () => {
// Setup event listeners
$dragContainer.addEventListener('dragenter', onDragEnter)
$dragContainer.addEventListener('dragover', onDragEnter)
$dragContainer.addEventListener('drop', async e => {
try {
await onDrop(e)
} catch (err) {
err.message = `Failed to add files: ${err.message}`
onError(err)
}
})
$dragContainer.addEventListener('dragleave', onDragLeave)
$fetchButton.addEventListener('click', async () => {
try {
await getFile()
} catch (err) {
err.message = `Failed to fetch CID: ${err.message}`
onError(err)
}
})
$connectButton.addEventListener('click', async () => {
try {
await connectToPeer()
} catch (err) {
err.message = `Failed to connect to peer: ${err.message}`
onError(err)
}
})
$workspaceBtn.addEventListener('click', () => {
window.location.hash = $workspaceInput.value
})
start()
}
startApplication()