1 | /*
|
2 | This is a shim to the IPFS library, (Lists are handled in YJS or OrbitDB)
|
3 | See https://github.com/ipfs/js-ipfs but note its often out of date relative to the generic API doc.
|
4 | */
|
5 |
|
6 | //TODO-IPFS Note API changes in https://github.com/ipfs/js-ipfs/issues/1721 probably all ipfs.files -> ipfs.
|
7 |
|
8 | const httptools = require('./httptools'); // Expose some of the httptools so that IPFS can use it as a backup
|
9 | const debug = require('debug')('dweb-transports:ipfs');
|
10 |
|
11 | // IPFS components
|
12 | let IPFS; //TODO-SPLIT move this line lower when fix structure
|
13 | //TODO-SPLIT remove this import depend on archive.html or node to pre-load
|
14 | //IPFS = require('ipfs');
|
15 | //TODO-SPLIT remove this import depend on archive.html or node to pre-load
|
16 | //const ipfsAPI = require('ipfs-http-client');
|
17 | //We now get this from IPFS.CID
|
18 | //const CID = require('cids');
|
19 |
|
20 | // Library packages other than IPFS
|
21 | const Url = require('url');
|
22 | const stream = require('readable-stream'); // Needed for the pullthrough - this is NOT Ipfs streams
|
23 |
|
24 | // Other Dweb modules
|
25 | const errors = require('./Errors'); // Standard Dweb Errors
|
26 | const Transport = require('./Transport.js'); // Base class for TransportXyz
|
27 | const Transports = require('./Transports'); // Manage all Transports that are loaded
|
28 | const utils = require('./utils'); // Utility functions
|
29 |
|
30 | const defaultoptions = {
|
31 | repo: '/tmp/dweb_ipfsv3107', //TODO-IPFS restarted 2018-10-06 because was caching connection ws-star
|
32 | //init: false,
|
33 | //start: false,
|
34 | //TODO-IPFS-Q how is this decentralized - can it run offline? Does it depend on star-signal.cloud.ipfs.team
|
35 | config: {
|
36 | // Addresses: { Swarm: [ '/dns4/star-signal.cloud.ipfs.team/wss/p2p-webrtc-star']}, // For Y - same as defaults
|
37 | // Addresses: { Swarm: [ ] }, // Disable WebRTC to test browser crash, note disables Y so doesnt work.
|
38 | //Addresses: {Swarm: ['/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star']}, // from https://github.com/ipfs/js-ipfs#faq 2017-12-05 as alternative to webrtc works sort-of
|
39 | //Bootstrap: ['/dns4/dweb.me/tcp/4245/wss/ipfs/QmPNgKEjC7wkpu3aHUzKKhZmbEfiGzL5TP1L8zZoHJyXZW'], // Connect via WSS to IPFS instance at IA on FnF
|
40 | Bootstrap: ['/dns4/dweb.me/tcp/4245/wss/ipfs/QmQz3p44VVQDeAieaW28DMjcTVzLbpxqaQB9bkXnyd7HY5'], // Connect via WSS to IPFS instance in Kube at IA
|
41 | // Previously was: QmQ921MRjsbP12fHSEDcdFeuHFg6qKDFurm2rXgA5K3RQD on kube
|
42 | },
|
43 | //init: true, // Comment out for Y
|
44 | EXPERIMENTAL: { pubsub: true },
|
45 | preload: { enabled: false },
|
46 | //Off by default, it never seems to have the content (routing issues) pass as an argument if want to use
|
47 | //httpIPFSgateway: "https://ipfs.io",
|
48 | };
|
49 |
|
50 | class TransportIPFS extends Transport {
|
51 | /*
|
52 | IPFS specific transport
|
53 |
|
54 | Fields:
|
55 | ipfs: object returned when starting IPFS
|
56 | */
|
57 |
|
58 | constructor(options) {
|
59 | super(options);
|
60 | [ "urlUrlstore", "httpIPFSgateway"].forEach(k => {
|
61 | this[k] = options[k];
|
62 | delete options[k];
|
63 | });
|
64 | this.ipfs = undefined; // Undefined till start IPFS
|
65 | this.options = options; // Dictionary of options
|
66 | this.name = "IPFS"; // For console log etc
|
67 | this.supportURLs = ['ipfs'];
|
68 | this.supportFunctions = ['fetch', 'store', 'seed', 'createReadStream']; // Does not support reverse
|
69 | this.supportFeatures = ['noCache']; // Note doesnt actually support noCache, but immutable is same
|
70 | this.status = Transport.STATUS_LOADED;
|
71 | }
|
72 |
|
73 | _ipfsversion(ipfs, s, cb) {
|
74 | ipfs.version((err, data) => {
|
75 | if (err) {
|
76 | debug("IPFS via %s present but unresponsive: %o", s, data);
|
77 | this.ipfstype = "FAILED";
|
78 | cb(err);
|
79 | } else {
|
80 | debug("IPFS available via %s: %o", s, data);
|
81 | this.ipfstype = s;
|
82 | cb(null, ipfs);
|
83 | }
|
84 | });
|
85 | }
|
86 | IPFSAutoConnect(cb) {
|
87 | IPFS = global.Ipfs || window.Ipfs; //Loaded by <script etc but still need a create
|
88 | const ipfsAPI = global.IpfsHttpClient || window.IpfsHttpClient;
|
89 | //TODO-SPLIT I think next few lines are wrong, dont think I've seen global.ipfs or window.ipfs but
|
90 | //TODO-SPLIT https://github.com/ipfs/js-ipfs implies global.Ipfs but needs a "create" or "new"
|
91 | if (global.ipfs) {
|
92 | this._ipfsversion(global.ipfs, "global.ipfs", cb );
|
93 | } else if (typeof window !== "undefined" && window.ipfs) {
|
94 | this._ipfsversion(window.ipfs, "window.ipfs", cb);
|
95 | } else {
|
96 | // noinspection ES6ConvertVarToLetConst
|
97 | var ipfs = ipfsAPI('localhost', '5001', {protocol: 'http'}); // leaving out the arguments will default to these values
|
98 | ipfs.version((err, data) => {
|
99 | if (err) {
|
100 | debug("IPFS via API failed %s, trying running own IPFS client", err.message);
|
101 | ipfs = new IPFS(this.options);
|
102 | ipfs.on('ready', () => {
|
103 | this._ipfsversion(ipfs, "client", cb);
|
104 | }); // This only works in the client version, not on API
|
105 | ipfs.on('error', (err) => {
|
106 | debug("IPFS via client error %s", err.message); // Calls error, note this could be a problem if it gets errors after "ready"
|
107 | cb(err);
|
108 | }) // This only works in the client version, not on API
|
109 | } else {
|
110 |
|
111 | this._ipfsversion(ipfs, "API", cb); // Note wastes an extra ipfs.version call but that's cheap
|
112 | }
|
113 | });
|
114 | }
|
115 | }
|
116 |
|
117 | /*OBS
|
118 | p_ipfsstart() {
|
119 | /-*
|
120 | Just start IPFS - not Y (note used with "yarrays" and will be used for non-IPFS list management)
|
121 | Note - can't figure out how to use async with this, as we resolve the promise based on the event callback
|
122 | *-/
|
123 | const self = this;
|
124 | return new Promise((resolve, reject) => {
|
125 | this.ipfs = new IPFS(this.options);
|
126 | this.ipfs.on('ready', () => {
|
127 | //this._makepromises();
|
128 | resolve();
|
129 | });
|
130 | this.ipfs.on('error', (err) => reject(err));
|
131 | })
|
132 | .then(() => self.ipfs.version())
|
133 | .then((version) => debug('ready %o',version))
|
134 | .catch((err) => {
|
135 | console.warn("IPFS p_ipfsstart failed", err.message);
|
136 | throw(err);
|
137 | });
|
138 | }
|
139 | */
|
140 |
|
141 | static setup0(options) {
|
142 | /*
|
143 | First part of setup, create obj, add to Transports but dont attempt to connect, typically called instead of p_setup if want to parallelize connections.
|
144 | */
|
145 | const combinedoptions = Transport.mergeoptions(defaultoptions, options.ipfs);
|
146 | debug("setup options=%o", combinedoptions);
|
147 | const t = new TransportIPFS(combinedoptions); // Note doesnt start IPFS
|
148 | Transports.addtransport(t);
|
149 | return t;
|
150 | }
|
151 |
|
152 | p_setup1(cbstatus, cb) {
|
153 | /* Start IPFS connection
|
154 | cbstatus function(this), for updating status, it must be ale to be called multiple times.
|
155 | returns this via cb(err,res) or promise
|
156 | errors This should never "fail" as it will break the Promise.all, it should return "this" but set this.status = Transport.STATUS_FAILED
|
157 | */
|
158 |
|
159 | if (cb) { try { f.call(this, cb) } catch(err) { cb(err)}} else { return new Promise((resolve, reject) => { try { f.call(this, (err, res) => { if (err) {reject(err)} else {resolve(res)} })} catch(err) {reject(err)}})} // Promisify pattern v2
|
160 | function f(cb) {
|
161 | // Logged by Transports
|
162 | this.status = Transport.STATUS_STARTING; // Should display, but probably not refreshed in most case
|
163 | if (cbstatus) cbstatus(this);
|
164 | this.IPFSAutoConnect((err, ipfs) => { // Various errors possible inc websocket
|
165 | if (err) {
|
166 | debug("Failed to connect %s", err.message);
|
167 | this.status = Transport.STATUS_FAILED;
|
168 | } else {
|
169 | this.ipfs = ipfs;
|
170 | this.status = Transport.STATUS_CONNECTED;
|
171 | }
|
172 | if (cbstatus) cbstatus(this);
|
173 | cb(null, this); // Don't fail, report the error and set statust to Transport.STATUS_FAILED
|
174 | })
|
175 | }
|
176 | }
|
177 |
|
178 | p_setup2(refreshstatus) {
|
179 | if (this.status === Transport.STATUS_FAILED) {
|
180 | debug("Stage 1 failed, skipping");
|
181 | }
|
182 | return this;
|
183 | }
|
184 |
|
185 | stop(refreshstatus, cb) { //TODO-API p_stop > stop
|
186 | if (this.ipfstype === "client") {
|
187 | this.ipfs.stop((err, res) => {
|
188 | this.status = Transport.STATUS_FAILED;
|
189 | if (refreshstatus) refreshstatus(this);
|
190 | cb(err, res);
|
191 | });
|
192 | } else {
|
193 | // We didn't start it, don't try and stop it
|
194 | this.status = Transport.STATUS_FAILED;
|
195 | if (refreshstatus) refreshstatus(this);
|
196 | cb(miull, this);
|
197 | }
|
198 | }
|
199 | async p_status() {
|
200 | /*
|
201 | Return a numeric code for the status of a transport.
|
202 | TODO - this no longer works if using the http api
|
203 | */
|
204 | this.status = (await this.ipfs.isOnline()) ? Transport.STATUS_CONNECTED : Transport.STATUS_FAILED;
|
205 | return super.p_status();
|
206 | }
|
207 |
|
208 | // Everything else - unless documented here - should be opaque to the actual structure of a CID
|
209 | // or a url. This code may change as its not clear (from IPFS docs) if this is the right mapping.
|
210 | static urlFrom(unknown) {
|
211 | /*
|
212 | Convert a CID into a standardised URL e.g. ipfs:/ipfs/abc123
|
213 | */
|
214 | if (unknown instanceof IPFS.CID) //TODO-SPLIT - I think there is a way to get this from a types array
|
215 | return "ipfs:/ipfs/"+unknown.toBaseEncodedString();
|
216 | if (typeof unknown === "object" && unknown.hash) // e.g. from files.add
|
217 | return "ipfs:/ipfs/"+unknown.hash;
|
218 | if (typeof unknown === "string") // Not used currently
|
219 | return "ipfs:/ipfs/"+unknown;
|
220 | throw new errors.CodingError("TransportIPFS.urlFrom: Cant convert to url from",unknown);
|
221 | }
|
222 |
|
223 | static cidFrom(url) {
|
224 | /*
|
225 | Convert a URL e.g. ipfs:/ipfs/abc123 into a CID structure suitable for retrieval
|
226 | url: String of form "ipfs://ipfs/<hash>" or parsed URL or CID
|
227 | returns: CID
|
228 | throws: TransportError if cant convert
|
229 | */
|
230 | if (url instanceof IPFS.CID) return url;
|
231 | if (typeof(url) === "string") url = Url.parse(url);
|
232 | if (url && url["pathname"]) { // On browser "instanceof Url" isn't valid)
|
233 | const patharr = url.pathname.split('/');
|
234 | if ((!["ipfs:","dweb:"].includes(url.protocol)) || (patharr[1] !== 'ipfs') || (patharr.length < 3))
|
235 | throw new errors.TransportError("TransportIPFS.cidFrom bad format for url should be dweb: or ipfs:/ipfs/...: " + url.href);
|
236 | if (patharr.length > 3)
|
237 | throw new errors.TransportError("TransportIPFS.cidFrom not supporting paths in url yet, should be dweb: or ipfs:/ipfs/...: " + url.href);
|
238 | return new IPFS.CID(patharr[2]);
|
239 | } else {
|
240 | throw new errors.CodingError("TransportIPFS.cidFrom: Cant convert url", url);
|
241 | }
|
242 | }
|
243 |
|
244 | static _stringFrom(url) {
|
245 | // Tool for ipfsFrom and ipfsGatewayFrom
|
246 | if (url instanceof IPFS.CID)
|
247 | return "/ipfs/"+url.toBaseEncodedString();
|
248 | if (typeof url === 'object' && url.path) { // It better be URL which unfortunately is hard to test
|
249 | return url.path;
|
250 | }
|
251 | }
|
252 | static ipfsFrom(url) {
|
253 | /*
|
254 | Convert to a ipfspath i.e. /ipfs/Qm....
|
255 | Required because of strange differences in APIs between files.cat and dag.get see https://github.com/ipfs/js-ipfs/issues/1229
|
256 | */
|
257 | url = this._stringFrom(url); // Convert CID or Url to a string hopefully containing /ipfs/
|
258 | if (url.indexOf('/ipfs/') > -1) {
|
259 | return url.slice(url.indexOf('/ipfs/'));
|
260 | }
|
261 | throw new errors.CodingError(`TransportIPFS.ipfsFrom: Cant convert url ${url} into a path starting /ipfs/`);
|
262 | }
|
263 |
|
264 | ipfsGatewayFrom(url) {
|
265 | /*
|
266 | url: CID, Url, or a string
|
267 | returns: https://ipfs.io/ipfs/<cid>
|
268 | */
|
269 | url = this._stringFrom(url); // Convert CID or Url to a string hopefully containing /ipfs/
|
270 | if (url.indexOf('/ipfs/') > -1) {
|
271 | return this.httpIPFSgateway + url.slice(url.indexOf('/ipfs/'));
|
272 | }
|
273 | throw new errors.CodingError(`TransportIPFS.ipfsGatewayFrom: Cant convert url ${url} into a path starting /ipfs/`);
|
274 | }
|
275 |
|
276 | static multihashFrom(url) {
|
277 | if (url instanceof IPFS.CID)
|
278 | return url.toBaseEncodedString();
|
279 | if (typeof url === 'object' && url.path)
|
280 | url = url.path; // /ipfs/Q...
|
281 | if (typeof(url) === "string") {
|
282 | const idx = url.indexOf("/ipfs/");
|
283 | if (idx > -1) {
|
284 | return url.slice(idx+6);
|
285 | }
|
286 | }
|
287 | throw new errors.CodingError(`Cant turn ${url} into a multihash`);
|
288 | }
|
289 |
|
290 | // noinspection JSCheckFunctionSignatures
|
291 | async p_rawfetch(url, {timeoutMS=60000, relay=false}={}) {
|
292 | /*
|
293 | Fetch some bytes based on a url of the form ipfs:/ipfs/Qm..... or ipfs:/ipfs/z.... .
|
294 | No assumption is made about the data in terms of size or structure, nor can we know whether it was created with dag.put or ipfs add or http /api/v0/add/
|
295 |
|
296 | Where required by the underlying transport it should retrieve a number if its "blocks" and concatenate them.
|
297 | Returns a new Promise that resolves currently to a string.
|
298 | There may also be need for a streaming version of this call, at this point undefined since we havent (currently) got a use case..
|
299 |
|
300 | :param string url: URL of object being retrieved {ipfs|dweb}:/ipfs/<cid> or /
|
301 | :resolve buffer: Return the object being fetched. (may in the future return a stream and buffer externally)
|
302 | :throws: TransportError if url invalid - note this happens immediately, not as a catch in the promise
|
303 | */
|
304 | // Attempt logged by Transports
|
305 | if (!url) throw new errors.CodingError("TransportIPFS.p_rawfetch: requires url");
|
306 | const cid = TransportIPFS.cidFrom(url); // Throws TransportError if url bad
|
307 | const ipfspath = TransportIPFS.ipfsFrom(url); // Need because dag.get has different requirement than file.cat
|
308 |
|
309 | try {
|
310 | const res = await utils.p_timeout(this.ipfs.dag.get(cid), timeoutMS, "Timed out IPFS fetch of "+TransportIPFS._stringFrom(cid)); // Will reject and throw TimeoutError if times out
|
311 | // noinspection Annotator
|
312 | if (res.remainderPath.length)
|
313 | { // noinspection ExceptionCaughtLocallyJS
|
314 | throw new errors.TransportError("Not yet supporting paths in p_rawfetch");
|
315 | }
|
316 | let buff;
|
317 | if (res.value.constructor.name === "DAGNode") { // Kludge to replace above, as its not matching the type against the "require" above.
|
318 | // We retrieved a DAGNode, call files.cat (the node will come from the cache quickly)
|
319 | buff = await this.ipfs.cat(ipfspath); //See js-ipfs v0.27 version and https://github.com/ipfs/js-ipfs/issues/1229 and https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#cat
|
320 | } else { //c: not a file
|
321 | debug("Found a raw IPFS block (unusual) - not a DAGNode - handling as such");
|
322 | buff = res.value;
|
323 | }
|
324 | // Success logged by Transports
|
325 | return buff;
|
326 | } catch (err) { // TimeoutError or could be some other error from IPFS etc
|
327 | debug("Caught error '%s' fetching via IPFS", err.message);
|
328 | if (!this.httpIPFSgateway) {
|
329 | throw(err);
|
330 | } else {
|
331 | try {
|
332 | debug("Trying IPFS HTTP gateway");
|
333 | let ipfsurl = this.ipfsGatewayFrom(url);
|
334 | return await utils.p_timeout(
|
335 | httptools.p_GET(ipfsurl), // Returns a buffer
|
336 | timeoutMS, "Timed out IPFS fetch of "+ipfsurl)
|
337 | } catch (err) {
|
338 | // Failure logged by Transports:
|
339 | //debug("Failed to retrieve from gateway: %s", err.message);
|
340 | throw err;
|
341 | }
|
342 | }
|
343 | }
|
344 | }
|
345 |
|
346 | async p_rawstore(data) {
|
347 | /*
|
348 | Store a blob of data onto the decentralised transport.
|
349 | Returns a promise that resolves to the url of the data
|
350 |
|
351 | :param string|Buffer data: Data to store - no assumptions made to size or content
|
352 | :resolve string: url of data stored
|
353 | */
|
354 | console.assert(data, "TransportIPFS.p_rawstore: requires data");
|
355 | const buf = (data instanceof Buffer) ? data : new Buffer(data);
|
356 | const res = (await this.ipfs.add(buf,{ "cid-version": 1, hashAlg: 'sha2-256'}))[0];
|
357 | return TransportIPFS.urlFrom(res);
|
358 | }
|
359 |
|
360 | seed({directoryPath=undefined, fileRelativePath=undefined, ipfsHash=undefined, urlToFile=undefined}, cb) {
|
361 | /* Note always passed a cb by Transports.seed - no need to support Promise here
|
362 | ipfsHash: IPFS hash if known (usually not known)
|
363 | urlToFile: Where the IPFS server can get the file - must be live before this called as will fetch and hash
|
364 | TODO support directoryPath/fileRelativePath, but to working around IPFS limitation in https://github.com/ipfs/go-ipfs/issues/4224 will need to check relative to IPFS home, and if not symlink it and add symlink
|
365 | TODO maybe support adding raw data (using add)
|
366 |
|
367 | Note neither js-ipfs-http-client nor js-ipfs appear to support urlstore yet, see https://github.com/ipfs/js-ipfs-http-client/issues/969
|
368 | */
|
369 | // This is the URL that the IPFS server uses to get the file from the local mirrorHttp
|
370 | if (!(this.urlUrlstore && urlToFile)) { // Not doing IPFS
|
371 | debug("IPFS.seed support requires urlUrlstore and urlToFile"); // Report, though Transports.seed currently ignores this
|
372 | cb(new Error("IPFS.seed support requires urlUrlstore and urlToFile")); // Report, though Transports.seed currently ignores this
|
373 | } else {
|
374 | // Building by hand becase of lack of support in js-ipfs-http-client
|
375 | const url = `${this.urlUrlstore}?arg=${encodeURIComponent(urlToFile)}`;
|
376 | // Have to be careful to avoid loops, the call to addIPFS should only be after file is retrieved and cached, and then addIPFS shouldnt be called if already cached
|
377 | httptools.p_GET(url, {retries:0}, (err, res) => {
|
378 | if (err) {
|
379 | debug("IPFS.seed for %s failed in http: %s", urlToFile, err.message);
|
380 | cb(err); // Note error currently ignored in Transports
|
381 | } else {
|
382 | debug("Added %s to IPFS key=", urlToFile, res.Key);
|
383 | // Check for mismatch - this isn't an error, for example it could be an updated file, old IPFS hash will now fail, but is out of date and shouldnt be shared
|
384 | if (ipfsHash && ipfsHash !== res.Key) { debug("ipfs hash doesnt match expected metadata has %s daemon returned %s", ipfsHash, res.Key); }
|
385 | cb(null, res)
|
386 | }
|
387 | })
|
388 | }
|
389 | }
|
390 | async p_f_createReadStream(url, {wanturl=false}={}) {
|
391 | /*
|
392 | Fetch bytes progressively, using a node.js readable stream, based on a url of the form:
|
393 | No assumption is made about the data in terms of size or structure.
|
394 |
|
395 | This is the initialisation step, which returns a function suitable for <VIDEO>
|
396 |
|
397 | Returns a new Promise that resolves to function for a node.js readable stream.
|
398 |
|
399 | Node.js readable stream docs: https://nodejs.org/api/stream.html#stream_readable_streams
|
400 |
|
401 | :param string url: URL of object being retrieved of form:
|
402 | magnet:xyzabc/path/to/file (Where xyzabc is the typical magnet uri contents)
|
403 | ipfs:/ipfs/Q123
|
404 | :param boolean wanturl True if want the URL of the stream (for service workers)
|
405 | :resolves to: f({start, end}) => stream (The readable stream.)
|
406 | :throws: TransportError if url invalid - note this happens immediately, not as a catch in the promise
|
407 | */
|
408 | // Logged by Transports;
|
409 | //debug("p_f_createreadstream %o", url);
|
410 | let stream;
|
411 | try {
|
412 | let multihash = url.pathname.split('/ipfs/')[1];
|
413 | if (multihash.includes('/'))
|
414 | { // noinspection ExceptionCaughtLocallyJS
|
415 | throw new CodingError("Should not be seeing URLS with a path here:"+url);
|
416 | }
|
417 | let self = this;
|
418 | if (wanturl) { // In ServiceWorker
|
419 | return url;
|
420 | } else {
|
421 | return function createReadStream(opts) {
|
422 | /*
|
423 | The function, encapsulated and inside another function by p_f_createReadStream (see docs)
|
424 | :param opts: { start: byte to start from; end: optional end byte }
|
425 | :returns stream: The readable stream.
|
426 | FOR IPFS this is copied and adapted from git repo js-ipfs/examples/browser-readablestream/index.js
|
427 | */
|
428 | debug("reading from stream %o %o", multihash, opts || "" );
|
429 |
|
430 | const start = opts ? opts.start : 0;
|
431 | // The videostream library does not always pass an end byte but when
|
432 | // it does, it wants bytes between start & end inclusive.
|
433 | // catReadableStream returns the bytes exclusive so increment the end
|
434 | // byte if it's been requested
|
435 | const end = (opts && opts.end) ? start + opts.end + 1 : undefined;
|
436 | // If we've streamed before, clean up the existing stream
|
437 | if (stream && stream.destroy) {
|
438 | stream.destroy()
|
439 | }
|
440 |
|
441 | // This stream will contain the requested bytes
|
442 |
|
443 | // For debugging used a known good IPFS video
|
444 | //let fakehash="QmedXJYwvNSJFRMVFuJt7BfCMcJwPoqJgqN3U2MYxHET5a"
|
445 | //console.log("XXX@IPFS.p_f_createReadStream faking call to",multihash, "with", fakehash)
|
446 | //multihash=fakehash;
|
447 | stream = self.ipfs.catReadableStream(multihash, {
|
448 | offset: start,
|
449 | length: end && end - start
|
450 | });
|
451 | // Log error messages
|
452 |
|
453 | stream.on('error', (err) => console.error(err));
|
454 |
|
455 | /* Gimmick from example :-)
|
456 | if (start === 0) {
|
457 | // Show the user some messages while we wait for the data stream to start
|
458 | statusMessages(stream, log)
|
459 | }
|
460 | */
|
461 | return stream
|
462 | };
|
463 | }
|
464 | } catch(err) {
|
465 | if (stream && stream.destroy) {
|
466 | stream.destroy()
|
467 | }
|
468 | // Error logged by Transports
|
469 | //console.log(`p_f_createReadStream failed on ${url} ${err.message}`);
|
470 | throw(err);
|
471 | }
|
472 | }
|
473 |
|
474 | static async p_test(opts) {
|
475 | {console.log("TransportIPFS.test")}
|
476 | try {
|
477 | const transport = await this.p_setup(opts); // Assumes IPFS already setup
|
478 | console.log(transport.name,"setup");
|
479 | const res = await transport.p_status();
|
480 | console.assert(res === Transport.STATUS_CONNECTED);
|
481 |
|
482 | let urlqbf;
|
483 | const qbf = "The quick brown fox";
|
484 | const qbf_url = "ipfs:/ipfs/zdpuAscRnisRkYnEyJAp1LydQ3po25rCEDPPEDMymYRfN1yPK"; // Expected url
|
485 | const testurl = "1114"; // Just a predictable number can work with
|
486 | const url = await transport.p_rawstore(qbf);
|
487 | console.log("rawstore returned", url);
|
488 | const newcid = TransportIPFS.cidFrom(url); // Its a CID which has a buffer in it
|
489 | console.assert(url === qbf_url, "url should match url from rawstore");
|
490 | const cidmultihash = url.split('/')[2]; // Store cid from first block in form of multihash
|
491 | const newurl = TransportIPFS.urlFrom(newcid);
|
492 | console.assert(url === newurl, "Should round trip");
|
493 | urlqbf = url;
|
494 | const data = await transport.p_rawfetch(urlqbf);
|
495 | console.assert(data.toString() === qbf, "Should fetch block stored above");
|
496 | //console.log("TransportIPFS test complete");
|
497 | return transport
|
498 | } catch(err) {
|
499 | console.log("Exception thrown in TransportIPFS.test:", err.message);
|
500 | throw err;
|
501 | }
|
502 | }
|
503 |
|
504 | }
|
505 | TransportIPFS.scripts=['ipfs/dist/index.min.js', // window.Ipfs 2.3Mb
|
506 | 'ipfs-http-client/dist/index.min.js']; //window.IpfsHttpClient
|
507 | TransportIPFS.requires=['ipfs', 'ipfs-http-client'];
|
508 |
|
509 |
|
510 | Transports._transportclasses["IPFS"] = TransportIPFS;
|
511 | // noinspection JSUndefinedPropertyAssignment
|
512 | exports = module.exports = TransportIPFS;
|