1 | const Transport = require('./Transport');
|
2 | const Transports = require('./Transports');
|
3 | const httptools = require('./httptools');
|
4 | const Url = require('url');
|
5 | const debug = require('debug')('dweb-transports:hash');
|
6 | const canonicaljson = require('@stratumn/canonicaljson');
|
7 |
|
8 | defaultHashOptions = {
|
9 | urlbase: 'https://dweb.me',
|
10 |
|
11 | };
|
12 |
|
13 | servercommands = {
|
14 | rawfetch: "contenthash",
|
15 | rawstore: "contenturl/rawstore",
|
16 | rawadd: "void/rawadd",
|
17 | rawlist: "metadata/rawlist",
|
18 | get: "get/table",
|
19 | set: "set/table",
|
20 | delete: "delete/table",
|
21 | keys: "keys/table",
|
22 | getall: "getall/table"
|
23 | };
|
24 |
|
25 |
|
26 | class TransportHASH extends Transport {
|
27 | |
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | constructor(options) {
|
39 | super(options);
|
40 | this.options = options;
|
41 | this.urlbase = options.urlbase;
|
42 | this.supportURLs = ['contenthash'];
|
43 | this.supportFunctions = ['fetch', 'store', 'add', 'list', 'reverse', 'newlisturls', "get", "set", "keys", "getall", "delete", "newtable", "newdatabase"];
|
44 | this.supportFeatures = ['noCache'];
|
45 | if (typeof window === "undefined") {
|
46 |
|
47 | this.supportFunctions.push("createReadStream");
|
48 | }
|
49 |
|
50 | this.supportFeatures = ['fetch.range', 'noCache'];
|
51 | this.name = "HASH";
|
52 | this.status = Transport.STATUS_LOADED;
|
53 | }
|
54 |
|
55 | static setup0(options) {
|
56 | let combinedoptions = Transport.mergeoptions(defaultHashOptions, options.hash);
|
57 | try {
|
58 | let t = new TransportHASH(combinedoptions);
|
59 | Transports.addtransport(t);
|
60 | return t;
|
61 | } catch (err) {
|
62 | debug("ERROR: HASH unable to setup0", err.message);
|
63 | throw err;
|
64 | }
|
65 | }
|
66 |
|
67 | p_setup2(statusCB) {
|
68 | this.http = Transports.http();
|
69 | return new Promise((resolve, unusedReject) => {
|
70 | this.status = Transport.STATUS_STARTING;
|
71 | if (statusCB) statusCB(this);
|
72 | this.updateStatus((unusedErr, unusedRes) => {
|
73 | if (statusCB) statusCB(this);
|
74 | this.startHeartbeat(this.options.heartbeat);
|
75 | resolve(this);
|
76 | });
|
77 | })
|
78 | }
|
79 |
|
80 | async p_status(cb) {
|
81 | |
82 |
|
83 |
|
84 | if (cb) { try { this.updateStatus(cb) } catch(err) { cb(err)}} else { return new Promise((resolve, reject) => { try { this.updateStatus((err, res) => { if (err) {reject(err)} else {resolve(res)} })} catch(err) {reject(err)}})}
|
85 | }
|
86 | updateStatus(cb) {
|
87 | this.updateInfo((err, res) => {
|
88 | if (err) {
|
89 | debug("Error status call to info failed %s", err.message);
|
90 | this.status = Transport.STATUS_FAILED;
|
91 | cb(null, this.status);
|
92 | } else {
|
93 | this.info = res;
|
94 | this.status = Transport.STATUS_CONNECTED;
|
95 | cb(null, this.status);
|
96 | }
|
97 | });
|
98 | }
|
99 |
|
100 | startHeartbeat({delay=undefined, statusCB=undefined}) {
|
101 | if (delay) {
|
102 | debug("%s Starting Heartbeat", this.name)
|
103 | this.heartbeatTimer = setInterval(() => {
|
104 | this.updateStatus((err, res)=>{
|
105 | if (statusCB) statusCB(this);
|
106 | }, (unusedErr, unusedRes)=>{});
|
107 | }, delay);
|
108 | }
|
109 | }
|
110 | stopHeartbeat() {
|
111 | if (this.heartbeatTimer) {
|
112 | debug("stopping heartbeat");
|
113 | clearInterval(this.heartbeatTimer);}
|
114 | }
|
115 | stop(refreshstatus, cb) {
|
116 | this.stopHeartbeat();
|
117 | this.status = Transport.STATUS_FAILED;
|
118 | if (refreshstatus) { refreshstatus(this); }
|
119 | cb(null, this);
|
120 | }
|
121 |
|
122 | _cmdurl(command) {
|
123 | return `${this.urlbase}/${command}`
|
124 | }
|
125 | _url(url, command, parmstr) {
|
126 | if (!url) throw new errors.CodingError(`${command}: requires url`);
|
127 | if (typeof url !== "string") { url = url.href }
|
128 | url = url.replace('contenthash:/contenthash', this._cmdurl(command)) ;
|
129 | url = url.replace('getall/table', command);
|
130 | url = url + (parmstr ? "?"+parmstr : "");
|
131 | return url;
|
132 | }
|
133 |
|
134 |
|
135 | async p_rawfetch(url, opts={}) {
|
136 | |
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 | if (((typeof url === "string") ? url : url.href).includes('/getall/table')) {
|
144 | throw new Error("Probably dont want to be calling p_rawfetch on a KeyValueTable, especially since dont know if its keyvaluetable or subclass");
|
145 | } else {
|
146 | return await this.http.p_rawfetch(this._url(url, servercommands.rawfetch), opts);
|
147 | }
|
148 | }
|
149 |
|
150 | p_rawlist(url) {
|
151 |
|
152 |
|
153 | if (!url) throw new errors.CodingError("TransportHASH.p_rawlist: requires url");
|
154 | return this.http.p_rawfetch(this._url(url, servercommands.rawlist));
|
155 | }
|
156 | rawreverse() { throw new errors.ToBeImplementedError("Undefined function TransportHASH.rawreverse"); }
|
157 |
|
158 | async p_rawstore(data) {
|
159 | |
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 | console.assert(data, "TransportHASH.p_rawstore: requires data");
|
167 | const res = await httptools.p_POST(this._cmdurl(servercommands.rawstore), {data, contenttype: "application/octet-stream"});
|
168 | let parsedurl = Url.parse(res);
|
169 | let pathparts = parsedurl.pathname.split('/');
|
170 | return `contenthash:/contenthash/${pathparts.slice(-1)}`
|
171 | }
|
172 |
|
173 | p_rawadd(url, sig) {
|
174 |
|
175 | if (!url || !sig) throw new errors.CodingError("TransportHASH.p_rawadd: invalid parms", url, sig);
|
176 | const data = canonicaljson.stringify(sig.preflight(Object.assign({},sig)))+"\n";
|
177 | return httptools.p_POST(this._url(url, servercommands.rawadd), {data, contenttype: "application/json"});
|
178 | }
|
179 |
|
180 | p_newlisturls(cl) {
|
181 | let u = cl._publicurls.map(urlstr => Url.parse(urlstr))
|
182 | .find(parsedurl =>
|
183 | ((parsedurl.protocol === "https:" && ["gateway.dweb.me", "dweb.me"].includes(parsedurl.host)
|
184 | && (parsedurl.pathname.includes('/content/rawfetch') || parsedurl.pathname.includes('/contenthash/')))
|
185 | || (parsedurl.protocol === "contenthash:") && (parsedurl.pathname.split('/')[1] === "contenthash")));
|
186 | if (!u) {
|
187 |
|
188 | u = `contenthash:/contenthash/${ cl.keypair.verifyexportmultihashsha256_58() }`;
|
189 | }
|
190 | return [u,u];
|
191 | }
|
192 |
|
193 |
|
194 |
|
195 | async p_f_createReadStream(url, {wanturl=false}={}) {
|
196 | return this.http.p_f_createReadStream(this._url(url, servercommands.rawfetch), {wanturl});
|
197 | }
|
198 |
|
199 | createReadStream(url, opts) {
|
200 | return this.http.createReadStream(this._url(url, servercommands.rawfetch), opts);
|
201 | }
|
202 |
|
203 | async p_createReadStream(url, opts) {
|
204 | return this.http.p_createReadStream(this._url(url, servercommands.rawfetch), opts);
|
205 | }
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | async p_newdatabase(pubkey) {
|
213 |
|
214 | if (pubkey.hasOwnProperty("keypair"))
|
215 | pubkey = pubkey.keypair.signingexport();
|
216 |
|
217 |
|
218 | let u = `${this.urlbase}/getall/table/${encodeURIComponent(pubkey)}`;
|
219 | return {"publicurl": u, "privateurl": u};
|
220 | }
|
221 |
|
222 |
|
223 | async p_newtable(pubkey, table) {
|
224 | if (!pubkey) throw new errors.CodingError("p_newtable currently requires a pubkey");
|
225 | let database = await this.p_newdatabase(pubkey);
|
226 |
|
227 | return { privateurl: `${database.privateurl}/${table}`, publicurl: `${database.publicurl}/${table}`}
|
228 | }
|
229 |
|
230 |
|
231 | async p_set(url, keyvalues, value) {
|
232 | if (!url || !keyvalues) throw new errors.CodingError("TransportHASH.p_set: invalid parms", url, keyvalyes);
|
233 |
|
234 |
|
235 | if (typeof keyvalues === "string") {
|
236 | let data = canonicaljson.stringify([{key: keyvalues, value: value}]);
|
237 | await httptools.p_POST(this._url(url, servercommands.set), {data, contenttype: "application/json"});
|
238 | } else {
|
239 | let data = canonicaljson.stringify(Object.keys(keyvalues).map((k) => ({"key": k, "value": keyvalues[k]})));
|
240 | await httptools.p_POST(this._url(url, servercommands.set), {data, contenttype: "application/json"});
|
241 | }
|
242 | }
|
243 |
|
244 | _keyparm(key) {
|
245 | return `key=${encodeURIComponent(key)}`
|
246 | }
|
247 | async p_get(url, keys) {
|
248 | if (!url && keys) throw new errors.CodingError("TransportHASH.p_get: requires url and at least one key");
|
249 | let parmstr =Array.isArray(keys) ? keys.map(k => this._keyparm(k)).join('&') : this._keyparm(keys);
|
250 | const res = await httptools.p_GET(this._url(url, servercommands.get, parmstr));
|
251 | return Array.isArray(keys) ? res : res[keys]
|
252 | }
|
253 |
|
254 | async p_delete(url, keys) {
|
255 | if (!url && keys) throw new errors.CodingError("TransportHASH.p_get: requires url and at least one key");
|
256 | let parmstr = keys.map(k => this._keyparm(k)).join('&');
|
257 | await httptools.p_GET(this._url(url, servercommands.delete, parmstr));
|
258 | }
|
259 |
|
260 | async p_keys(url) {
|
261 | if (!url && keys) throw new errors.CodingError("TransportHASH.p_get: requires url and at least one key");
|
262 | return await httptools.p_GET(this._url(url, servercommands.keys));
|
263 | }
|
264 | async p_getall(url) {
|
265 | if (!url && keys) throw new errors.CodingError("TransportHASH.p_get: requires url and at least one key");
|
266 | return await httptools.p_GET(this._url(url, servercommands.getall));
|
267 | }
|
268 | |
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 | async p_info() {
|
278 | |
279 |
|
280 |
|
281 | return new Promise((resolve, reject) => { try { this.updateInfo((err, res) => { if (err) {reject(err)} else {resolve(res)} })} catch(err) {reject(err)}})
|
282 | }
|
283 |
|
284 | updateInfo(cb) {
|
285 | httptools.p_GET(`${this.urlbase}/info`, {retries: 1}, cb);
|
286 | }
|
287 |
|
288 | static async p_test(opts={}) {
|
289 | {console.log("TransportHASH.test")}
|
290 | try {
|
291 | let transport = await this.p_setup(opts);
|
292 | console.log("HASH connected");
|
293 | let res = await transport.p_info();
|
294 | console.log("TransportHASH info=",res);
|
295 | res = await transport.p_status();
|
296 | console.assert(res === Transport.STATUS_CONNECTED);
|
297 | await transport.p_test_kvt("NACL%20VERIFY");
|
298 | } catch(err) {
|
299 | console.log("Exception thrown in TransportHASH.test:", err.message);
|
300 | throw err;
|
301 | }
|
302 | }
|
303 |
|
304 | static async test() {
|
305 | return this;
|
306 | }
|
307 |
|
308 | }
|
309 | Transports._transportclasses["HASH"] = TransportHASH;
|
310 | TransportHASH.requires = TransportHASH.scripts = [];
|
311 | exports = module.exports = TransportHASH;
|
312 |
|