"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto = require("libp2p-crypto"); const util = require("util"); const storage_1 = require("./storage"); const TextEncoder = util.TextEncoder; const ws_1 = __importDefault(require("ws")); const util_1 = require("./util"); const webclient_node_1 = __importDefault(require("./webclient-node")); const content_item_1 = require("./content-item"); class BankClient { constructor(urlBase, ipfsUrlBase, storage = new storage_1.Storage('bankClient'), webClient = new webclient_node_1.default()) { this.urlBase = urlBase; this.ipfsUrlBase = ipfsUrlBase; this.storage = storage; this.webClient = webClient; this.wsUrlBase = urlBase.replace(/^http/i, 'ws'); } static parseBankLink(bankLink) { if (!bankLink.startsWith('bank:')) { throw new Error('address must start with bank:'); } const deprefixed = bankLink.substring(5); let host; let address; let topic; if (deprefixed[0] === '/' && deprefixed[1] === '/') { [host, address, topic] = deprefixed.substring(2).split('/'); } else { [address, topic] = deprefixed.split('/'); } if (!address || !topic) { throw new Error('cannot parse address and topic'); } return { host, address, topic }; } getPub() { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { yield this.bootstrap(); this.getPriv().id((idErr, pubHash) => { if (idErr) { return reject(idErr); } resolve(pubHash); }); })); } bootstrap() { if (this.bootstrapResult) { return Promise.resolve(this.bootstrapResult); } if (this.bootstrapPromise) { return this.bootstrapPromise; } return this.bootstrapPromise = new Promise((resolve, reject) => { this.storage.get('notaprivatekey').then(privateKeyFromStorage => { if (privateKeyFromStorage == null) { console.log('no private key in storage. generating new'); crypto.keys.generateKeyPair('RSA', 2048, (generateErr, privateKey) => { if (generateErr) { return reject(generateErr); } privateKey.export('password', (exportErr, exportResult) => { if (exportErr) { return reject(exportErr); } this.storage.set('notaprivatekey', exportResult).then(err => { // whatever }).catch(reject); this.privateKey = privateKey; resolve(true); }); }); } else { // console.log('importing privatekey'); crypto.keys.import(privateKeyFromStorage, 'password', (err, importedPrivateKey) => { if (err) { return reject(err); } this.privateKey = importedPrivateKey; // console.log(this.getPublicKeyString()); // console.log(privateKeyFromStorage); resolve(true); }); } }).catch(reject); }); } getNonce() { return __awaiter(this, void 0, void 0, function* () { const nonce = yield this.webClient.request({ method: 'GET', url: this.urlBase + '/bank/nonce' }); return Number(nonce); }); } getBalance() { return __awaiter(this, void 0, void 0, function* () { const nonce = yield this.getNonce(); const retrieveRequest = yield this.makePlaintextPayload(JSON.stringify({ _date: new Date().toISOString(), _nonce: nonce })); const topicURL = this.urlBase + '/bank/getbalance'; const postResponse = yield this.webClient.requestJSON({ body: retrieveRequest, method: 'POST', url: topicURL }); return postResponse.balance; }); } upload(params) { return __awaiter(this, void 0, void 0, function* () { const url = this.urlBase + '/bank/upload'; const formData = {}; if (params.fileData) { formData.file = { value: params.fileData, options: { filename: params.fileName } }; } if (params.thumbFileData) { formData.thumb = { value: params.thumbFileData, options: { filename: params.thumbFileName } }; } if (params.links) { formData.links = JSON.stringify(params.links); } for (const attr of ['title', 'text', 'type']) { if (params[attr] != null) { formData[attr] = params[attr]; } } // console.log('formData', formData); const uploadResponse = yield this.webClient.requestJSON({ formData, method: 'POST', url }); // console.log('uploadResponse', uploadResponse); return uploadResponse.hash; }); } uploadSlimJSON(item) { return __awaiter(this, void 0, void 0, function* () { const url = this.urlBase + '/bank/upload/slim'; const uploadResponse = yield this.webClient.requestJSON({ body: item, method: 'POST', url }); // console.log('uploadResponse', uploadResponse); return uploadResponse.hash; }); } uploadSlimText(item) { return __awaiter(this, void 0, void 0, function* () { const url = this.urlBase + '/bank/upload/slim'; const uploadResponse = JSON.parse(yield this.webClient.request({ body: item, headers: { 'content-type': 'text/plain' }, method: 'POST', url })); // console.log('uploadResponse', uploadResponse); return uploadResponse.hash; }); } appendBank(bankAddress, bankTopic, itemHash) { return __awaiter(this, void 0, void 0, function* () { const payload = yield this.makePlaintextPayload(itemHash); const topicURL = this.urlBase + '/bank/private/' + encodeURIComponent(bankAddress) + '/' + encodeURIComponent(bankTopic); yield this.webClient.requestJSON({ body: payload, method: 'PUT', url: topicURL }); }); } retrievePrivate(peerAddr, topic) { return __awaiter(this, void 0, void 0, function* () { const nonce = yield this.getNonce(); const retrieveRequest = yield this.makePlaintextPayload(JSON.stringify({ _date: new Date().toISOString(), _nonce: nonce })); const topicURL = this.urlBase + '/bank/private/' + encodeURIComponent(peerAddr) + '/' + encodeURIComponent(topic); const result = yield this.webClient.request({ body: JSON.stringify(retrieveRequest), headers: { 'content-type': 'application/json' }, method: 'POST', url: topicURL }); return result; }); } subscribePrivate(peerAddr, topic, callback) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const nonce = yield this.getNonce(); const retrieveRequest = yield this.makePlaintextPayload(JSON.stringify({ _date: new Date().toISOString(), _nonce: nonce, addr: peerAddr, topic: topic })); const ws = new ws_1.default(this.wsUrlBase + '/bank/ws', { headers: { 'x-msg': retrieveRequest.msg, 'x-pub': retrieveRequest.pub, 'x-pubhash': retrieveRequest.pubHash, 'x-sig': retrieveRequest.sig } }); ws.on('open', function open() { console.log('opened ws'); resolve(); }); ws.on('message', data => { // console.log('ws message', data); callback(); }); ws.on('error', err => { reject(err); }); })); } appendPrivate(peerAddr, topic, hash, replaceHash) { return __awaiter(this, void 0, void 0, function* () { const nonce = yield this.getNonce(); const payload = yield this.makePlaintextPayload(JSON.stringify({ _date: new Date().toISOString(), _nonce: nonce, hash, replaceHash })); const topicURL = this.urlBase + '/bank/private/' + encodeURIComponent(peerAddr) + '/' + encodeURIComponent(topic); const result = yield this.webClient.request({ body: JSON.stringify(payload), headers: { 'content-type': 'application/json' }, method: 'PUT', url: topicURL }); }); } getOrCreateContact(peerId, contactAddr) { return __awaiter(this, void 0, void 0, function* () { const contactList = yield this.retrievePrivate(peerId, '📇'); const itemList = yield this.getItemsForCommaList(contactList); // console.log('contact hash for', contact, type, 'is', contactHash); const existing = itemList.filter(item => item.addrs && item.addrs.includes(contactAddr))[0]; if (existing != null) { return existing; } const newItem = { addrs: [ contactAddr ], id: util_1.uuid() }; const newItemHash = yield this.uploadSlimJSON(newItem); yield this.appendPrivate(peerId, '📇', newItemHash); return newItem; }); } getContactById(peerId, contactId) { return __awaiter(this, void 0, void 0, function* () { const contactList = yield this.retrievePrivate(peerId, '📇'); const itemList = yield this.getItemsForCommaList(contactList); const existing = itemList.filter(item => item.id === contactId)[0]; if (!existing) { throw new Error('Cannot find contact with id ' + contactId); } return existing; }); } updateContact(peerId, contactId, newProperties) { return __awaiter(this, void 0, void 0, function* () { const existing = yield this.getContactById(peerId, contactId); const newProps = util_1.mergeDeep({}, newProperties); delete newProps.id; const newItem = util_1.mergeDeep(existing, newProps); delete newItem.hash; const newItemHash = yield this.uploadSlimJSON(newItem); yield this.appendPrivate(peerId, '📇', newItemHash, existing.hash); return yield this.getContactById(peerId, contactId); }); } getContentItemByHash(hash) { return __awaiter(this, void 0, void 0, function* () { if (!hash.startsWith('/ipfs/')) { hash = '/ipfs/' + hash; } const contentParams = (yield this.webClient.requestJSON({ method: 'get', url: this.ipfsUrlBase + hash + '/content.json' })); return new content_item_1.ContentItem(contentParams); }); } getItemsForCommaList(commaList) { return __awaiter(this, void 0, void 0, function* () { const itemHashes = commaList.split(',').filter(x => x.trim() !== ''); const items = yield Promise.all(itemHashes.map(itemId => { return this.webClient.requestJSON({ method: 'get', url: this.ipfsUrlBase + '/ipfs/' + itemId, }); })); for (const item of items) { item.hash = itemHashes.shift(); } return items; }); } getPriv() { if (!this.privateKey) { throw new Error('missing private key'); } return this.privateKey; } makePlaintextPayload(message) { const messageBytes = new TextEncoder().encode(message); return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { yield this.bootstrap(); this.privateKey.sign(messageBytes, (signErr, signatureBytes) => __awaiter(this, void 0, void 0, function* () { if (signErr) { reject(signErr); return; } const publicDERBytes = this.privateKey.public.bytes; this.privateKey.id((idErr, pubHash) => { if (idErr) { reject(idErr); return; } const result = { date: new Date().toISOString(), msg: util_1.encodeHex(messageBytes), pub: util_1.encodeHex(publicDERBytes), pubHash, sig: util_1.encodeHex(signatureBytes), }; // console.log('result', result, signatureBytes); resolve(result); }); })); })); } } exports.BankClient = BankClient; //# sourceMappingURL=index.js.map