Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
383 lines (310 sloc)
10.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* 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() |