index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. return new (P || (P = Promise))(function (resolve, reject) {
  4. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  5. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  6. function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
  7. step((generator = generator.apply(thisArg, _arguments || [])).next());
  8. });
  9. };
  10. var __importDefault = (this && this.__importDefault) || function (mod) {
  11. return (mod && mod.__esModule) ? mod : { "default": mod };
  12. };
  13. Object.defineProperty(exports, "__esModule", { value: true });
  14. const crypto = require("libp2p-crypto");
  15. const util = require("util");
  16. const storage_1 = require("./storage");
  17. const TextEncoder = util.TextEncoder;
  18. const util_1 = require("./util");
  19. const webclient_node_1 = __importDefault(require("./webclient-node"));
  20. class BankClient {
  21. constructor(urlBase, ipfsUrlBase, storage = new storage_1.Storage('bankClient'), webClient = new webclient_node_1.default()) {
  22. this.urlBase = urlBase;
  23. this.ipfsUrlBase = ipfsUrlBase;
  24. this.storage = storage;
  25. this.webClient = webClient;
  26. }
  27. getPub() {
  28. return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
  29. yield this.bootstrap();
  30. this.getPriv().id((idErr, pubHash) => {
  31. if (idErr) {
  32. return reject(idErr);
  33. }
  34. resolve(pubHash);
  35. });
  36. }));
  37. }
  38. bootstrap() {
  39. if (this.bootstrapResult) {
  40. return Promise.resolve(this.bootstrapResult);
  41. }
  42. if (this.bootstrapPromise) {
  43. return this.bootstrapPromise;
  44. }
  45. return this.bootstrapPromise = new Promise((resolve, reject) => {
  46. this.storage.get('notaprivatekey').then(privateKeyFromStorage => {
  47. if (privateKeyFromStorage == null) {
  48. console.log('no private key in storage. generating new');
  49. crypto.keys.generateKeyPair('RSA', 2048, (generateErr, privateKey) => {
  50. if (generateErr) {
  51. return reject(generateErr);
  52. }
  53. privateKey.export('password', (exportErr, exportResult) => {
  54. if (exportErr) {
  55. return reject(exportErr);
  56. }
  57. this.storage.set('notaprivatekey', exportResult).then(err => {
  58. // whatever
  59. }).catch(reject);
  60. this.privateKey = privateKey;
  61. resolve(true);
  62. });
  63. });
  64. }
  65. else {
  66. // console.log('importing privatekey');
  67. crypto.keys.import(privateKeyFromStorage, 'password', (err, importedPrivateKey) => {
  68. if (err) {
  69. return reject(err);
  70. }
  71. this.privateKey = importedPrivateKey;
  72. // console.log(this.getPublicKeyString());
  73. // console.log(privateKeyFromStorage);
  74. resolve(true);
  75. });
  76. }
  77. }).catch(reject);
  78. });
  79. }
  80. getNonce() {
  81. return __awaiter(this, void 0, void 0, function* () {
  82. const nonce = yield this.webClient.request({
  83. method: 'GET',
  84. url: this.urlBase + '/bank/nonce'
  85. });
  86. return Number(nonce);
  87. });
  88. }
  89. getBalance() {
  90. return __awaiter(this, void 0, void 0, function* () {
  91. const nonce = yield this.getNonce();
  92. const retrieveRequest = yield this.makePlaintextPayload(JSON.stringify({
  93. _date: new Date().toISOString(),
  94. _nonce: nonce
  95. }));
  96. const topicURL = this.urlBase + '/bank/getbalance';
  97. const postResponse = yield this.webClient.requestJSON({
  98. body: retrieveRequest,
  99. method: 'POST',
  100. url: topicURL
  101. });
  102. return postResponse.balance;
  103. });
  104. }
  105. upload(params) {
  106. return __awaiter(this, void 0, void 0, function* () {
  107. const url = this.urlBase + '/bank/upload';
  108. const formData = {};
  109. if (params.fileData) {
  110. formData.file = {
  111. value: params.fileData,
  112. options: {
  113. filename: params.fileName
  114. }
  115. };
  116. }
  117. if (params.thumbFileData) {
  118. formData.thumb = {
  119. value: params.thumbFileData,
  120. options: {
  121. filename: params.thumbFileName
  122. }
  123. };
  124. }
  125. for (const attr of ['title', 'text', 'type']) {
  126. if (params[attr] != null) {
  127. formData[attr] = params[attr];
  128. }
  129. }
  130. // console.log('formData', formData);
  131. const uploadResponse = yield this.webClient.requestJSON({
  132. formData,
  133. method: 'POST',
  134. url
  135. });
  136. // console.log('uploadResponse', uploadResponse);
  137. return uploadResponse.hash;
  138. });
  139. }
  140. uploadSlimJSON(item) {
  141. return __awaiter(this, void 0, void 0, function* () {
  142. const url = this.urlBase + '/bank/upload/slim';
  143. const uploadResponse = yield this.webClient.requestJSON({
  144. body: item,
  145. method: 'POST',
  146. url
  147. });
  148. // console.log('uploadResponse', uploadResponse);
  149. return uploadResponse.hash;
  150. });
  151. }
  152. uploadSlimText(item) {
  153. return __awaiter(this, void 0, void 0, function* () {
  154. const url = this.urlBase + '/bank/upload/slim';
  155. const uploadResponse = JSON.parse(yield this.webClient.request({
  156. body: item,
  157. headers: {
  158. 'content-type': 'text/plain'
  159. },
  160. method: 'POST',
  161. url
  162. }));
  163. // console.log('uploadResponse', uploadResponse);
  164. return uploadResponse.hash;
  165. });
  166. }
  167. appendBank(bankLink, itemHash) {
  168. return __awaiter(this, void 0, void 0, function* () {
  169. const payload = yield this.makePlaintextPayload(itemHash);
  170. const { address, topic } = this.parseBankLink(bankLink);
  171. const topicURL = this.urlBase + '/bank/private/' + encodeURIComponent(address) + '/' + encodeURIComponent(topic);
  172. yield this.webClient.requestJSON({
  173. body: payload,
  174. method: 'PUT',
  175. url: topicURL
  176. });
  177. });
  178. }
  179. retrievePrivate(peerAddr, topic) {
  180. return __awaiter(this, void 0, void 0, function* () {
  181. const nonce = yield this.getNonce();
  182. const retrieveRequest = yield this.makePlaintextPayload(JSON.stringify({
  183. _date: new Date().toISOString(),
  184. _nonce: nonce
  185. }));
  186. const topicURL = this.urlBase + '/bank/private/' + encodeURIComponent(peerAddr) + '/' + encodeURIComponent(topic);
  187. const result = yield this.webClient.request({
  188. body: JSON.stringify(retrieveRequest),
  189. headers: {
  190. 'content-type': 'application/json'
  191. },
  192. method: 'POST',
  193. url: topicURL
  194. });
  195. return result;
  196. });
  197. }
  198. appendPrivate(peerAddr, topic, hash) {
  199. return __awaiter(this, void 0, void 0, function* () {
  200. const payload = yield this.makePlaintextPayload(hash);
  201. const topicURL = this.urlBase + '/bank/private/' + encodeURIComponent(peerAddr) + '/' + encodeURIComponent(topic);
  202. const result = yield this.webClient.request({
  203. body: JSON.stringify(payload),
  204. headers: {
  205. 'content-type': 'application/json'
  206. },
  207. method: 'PUT',
  208. url: topicURL
  209. });
  210. });
  211. }
  212. getOrCreateContact(peerAddr, contact, type) {
  213. return __awaiter(this, void 0, void 0, function* () {
  214. const contactList = yield this.retrievePrivate(peerAddr, '📇');
  215. const itemList = yield this.getItemsForCommaList(contactList);
  216. const contactHash = yield this.getContactHash(contact, type);
  217. // console.log('contact hash for', contact, type, 'is', contactHash);
  218. const existing = itemList.filter(item => item.contactHash === contactHash)[0];
  219. if (existing != null) {
  220. return existing;
  221. }
  222. const newItem = {
  223. contact,
  224. contactHash,
  225. type,
  226. };
  227. const newItemHash = yield this.uploadSlimJSON(newItem);
  228. yield this.appendPrivate(peerAddr, '📇', newItemHash);
  229. return newItem;
  230. });
  231. }
  232. getContactHash(contact, type) {
  233. return __awaiter(this, void 0, void 0, function* () {
  234. const value = type + ':' + contact;
  235. return yield this.uploadSlimText(value);
  236. });
  237. }
  238. getItemsForCommaList(commaList) {
  239. return __awaiter(this, void 0, void 0, function* () {
  240. // console.log('commaList', commaList);
  241. const itemHashes = commaList.split(',').filter(x => x.trim() !== '');
  242. // console.log('itemHashes', itemHashes);
  243. return yield Promise.all(itemHashes.map(itemId => {
  244. return this.webClient.requestJSON({
  245. method: 'get',
  246. url: this.ipfsUrlBase + '/ipfs/' + itemId,
  247. });
  248. }));
  249. });
  250. }
  251. getPriv() {
  252. if (!this.privateKey) {
  253. throw new Error('missing private key');
  254. }
  255. return this.privateKey;
  256. }
  257. makePlaintextPayload(message) {
  258. const messageBytes = new TextEncoder().encode(message);
  259. return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
  260. yield this.bootstrap();
  261. this.privateKey.sign(messageBytes, (signErr, signatureBytes) => __awaiter(this, void 0, void 0, function* () {
  262. if (signErr) {
  263. reject(signErr);
  264. return;
  265. }
  266. const publicDERBytes = this.privateKey.public.bytes;
  267. this.privateKey.id((idErr, pubHash) => {
  268. if (idErr) {
  269. reject(idErr);
  270. return;
  271. }
  272. const result = {
  273. date: new Date().toISOString(),
  274. msg: util_1.encodeHex(messageBytes),
  275. pub: util_1.encodeHex(publicDERBytes),
  276. pubHash,
  277. sig: util_1.encodeHex(signatureBytes),
  278. };
  279. // console.log('result', result, signatureBytes);
  280. resolve(result);
  281. });
  282. }));
  283. }));
  284. }
  285. parseBankLink(bankLink) {
  286. if (!bankLink.startsWith('bank:')) {
  287. throw new Error('address must start with bank:');
  288. }
  289. const [address, topic] = bankLink.substring(5).split('/');
  290. if (!address || !topic) {
  291. throw new Error('cannot parse address and topic');
  292. }
  293. return { address, topic };
  294. }
  295. }
  296. exports.BankClient = BankClient;
  297. //# sourceMappingURL=index.js.map