上传!上传!
IPFS 是星际文件系统(InterPlanetary File System)的简称,它为前端应用展现了一种可能性:大部分轻量应用其实可以只开发前端,不需要后端!
如果你的应用只涉及轻量数据的慢速存取,那就可以来试试看用 IPFS 来从分布式存储网络上获取数据,再把用户产生的数据写到 IPFS 上。
以下是现在大家使用 IPFS 的几种招式。
[1] 带有进度条的网页端上传
原料:
- stream-buffers 用于提供进度条的 stream
- buffer 用于把文件变成能喂给 stream 的类型
在线地址:
由于 codeSandbox 上 IPFS 会有点问题,所以还请 clone github repo 来试用。
// @flow
import IPFS from 'ipfs';
import { Buffer } from 'buffer';
import streamBuffers from 'stream-buffers';
class IPFSUploader {
node: any;
progress: number;
stream: any;
constructor(http: Http) {
// 用随机的仓库地址(IPFS 在本地缓存数据的地方)来初始化 IPFS 节点
const repoPath = 'ipfs-' + Math.random();
this.node = new IPFS({ repo: repoPath });
// 节点完成初始化并开始连接其他节点后会触发 ready 事件
this.node.on('ready', () => console.log('Online status: ', this.node.isOnline() ? 'online' : 'offline'));
}
uploadIPFS = (fileArrayBuffer: ArrayBuffer): Promise<Buffer> => {
return new Promise((resolve, reject) => {
// 先设置进度条到 0 的位置
this.progress = 0;
// 创建用于修改进度条进度的流
const myReadableStreamBuffer = new streamBuffers.ReadableStreamBuffer({
chunkSize: 25000, // 决定了传输速率
});
// 修改进度条进度
myReadableStreamBuffer.on('data', (chunk: Buffer) => {
this.progress += chunk.byteLength;
myReadableStreamBuffer.resume();
});
// 创建 IPFS 读写文件的流,这是一个 Duplex 流,可读可写
this.stream = this.node.files.addReadableStream();
// 文件上传完毕后 resolve 这个 Promise
this.stream.on('data', (file: Buffer) => resolve(file));
// 对接好两个流,并开始上传
this.stream.write(myReadableStreamBuffer);
myReadableStreamBuffer.put(Buffer.from(fileArrayBuffer));
// 上传完毕后关闭流
myReadableStreamBuffer.on('end', this.stream.end);
myReadableStreamBuffer.stop();
});
};
}
// 从用户处获取要上传的文件
const uploader = new IPFSUploader();
function onUpload(event: SyntheticInputEvent<EventTarget>) {
const file: File = event.target.file;
// 用 FileReader API
// https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
const reader = new FileReader();
reader.onload = async () => {
// FileReader 在 readAsArrayBuffer 结束后,会把 ArrayBuffer 放在 reader.result 里
const ipfsObject: { path: string, hash: string, size: number } = await uploader.uploadIPFS(reader.result);
const URL = `https://ipfs.io/ipfs/${ipfsObject.hash}`;
};
reader.readAsArrayBuffer(file);
}
[2] 自动下载 go-ipfs 用于启动 daemon
原料:
- @akashaproject/ipfs-connector 用于在 nodeJS(Electron)环境下自动下载 ipfs 二进制文件启动 daemon
// @flow
import { IpfsConnector, ipfsEvents, ConnectorState } from '@akashaproject/ipfs-connector';
const instance = IpfsConnector.getInstance();
const repoPathOnFs = '~/ipfs-test-repo';
// 如果电脑上没用 IPFS 的可执行文件,就会把可执行文件下载到这里
instance.setBinPath(repoPathOnFs);
// 设置 IPFS 本地 repo 的位置
instance.setIpfsFolder(path.join(repoPathOnFs, '/repo'));
instance.setOption('args', ['daemon', '--enable-pubsub-experiment']);
instance.enableDownloadEvents();
// install some event listeners
instance.on(ipfsEvents.SERVICE_STARTING, onServiceStarting);
instance.on(ipfsEvents.SERVICE_STARTED, onServiceStarted);
instance.on(ipfsEvents.UPGRADING_BINARY, onServiceUpgrade);
instance.on(ipfsEvents.SERVICE_STOPPING, onServiceStopping);
instance.on(ipfsEvents.SERVICE_FAILED, onServiceFailed);
instance.on(ipfsEvents.STATUS_UPDATE, onStatusUpdate);
instance.on(ipfsEvents.DOWNLOAD_PROGRESS, onDownloadProgress);
instance.on(ipfsEvents.DOWNLOAD_ERROR, onDownloadError);
// 启动 IPFS daemon,如果电脑上没有装 IPFS 就会自动下载到 setBinPath 设置的地址里
instance.start();
const stop = () => {
// 重新获取单例,用它下线 IPFS 节点
const instance = IpfsConnector.getInstance();
instance.stop();
};
这段代码不方便执行,因为它会自动从 https://dist.ipfs.io/go-ipfs/v0.4.13/go-ipfs_v0.4.13_darwin-amd64.tar.gz
下载二进制文件,但是给 ipfs.io 签发数字证书的 CA 被墙了,所以在程序里会报错 RequestError: Hostname/IP does not match certificate's altnames: Host: dist.ipfs.io. is not in the cert's altnames: DNS:api.twitter.com
,如果在浏览器里不翻墙访问这个地址也会说这个 HTTPS 有问题。翻墙了就没事,但作为开发者,我们没法保证用户都翻墙,对吧。