UNPKG

@cronstamp/clientlib

Version:

Client library for cronstamp, a blockchain-based document timestamping and verification service.

176 lines 9.33 kB
import { getRequest, postRequest } from '../util/api.js'; import { AdditionalDataType, CertificateProcess, ErrorTypes, InputDataType, MessageType, ProcessError, Step } from './manager.js'; import { VerificationProcess } from './verify.js'; import { sleep_ms, sleep_s } from '../util/time.js'; /** * Start certificate request for a string * @param message * @param blockchainsToRequire */ export function requestCertificateForString(message, blockchainsToRequire = undefined) { return new RequestProcess({ type: InputDataType.STRING, data: message, blockchainsToRequire: blockchainsToRequire }); } /** * Start certificate request for a file * @param file * @param blockchainsToRequire */ export function requestCertificateForFile(file, blockchainsToRequire = undefined) { return new RequestProcess({ type: InputDataType.FILE, data: file, blockchainsToRequire: blockchainsToRequire }); } /** * Start certificate request for an existing hash * @param hash base64 encoded hash * @param blockchainsToRequire */ export function requestCertificateForHash(hash, blockchainsToRequire = undefined) { return new RequestProcess({ type: InputDataType.HASH, data: hash, blockchainsToRequire: blockchainsToRequire }); } /** * Start certificate request * @param type * @param data * @param blockchainsToRequire */ export function requestCertificate(type, data, blockchainsToRequire = undefined) { return new RequestProcess({ type: type, data: data, blockchainsToRequire: blockchainsToRequire }); } export class RequestProcess extends CertificateProcess { constructor(data) { super(data, 'Request'); } async start() { // after super.start() a certificate with document_hash is available let preparation = await super.start(); if (preparation.step != Step.HASH_INPUT_DATA || preparation.type != MessageType.SUCCESS) { return preparation; } this.step = Step.REQUEST_SUBMIT_DOCUMENT_HASH; // check for invalid blockchains in list try { this.log.debug('Sending request to generate certificate for hash ' + this.certHelper.cert.document_hash); let submitResponse = await postRequest(`/v${this.certHelper.cert.meta.version}/submit/`, [ { document_hash: this.certHelper.cert.document_hash, blockchains: this.certHelper.cert.meta.blockchains } ], this.config); let submitResult = await submitResponse.json(); let estimatedAvailabilityTimes = submitResult[0].estimated_availability_times; this.log.debug('Availability times received from API:', estimatedAvailabilityTimes); this.sendMessage('Received response JSON:', MessageType.SUCCESS, AdditionalDataType.HTTP_RESPONSE_JSON, submitResult); this.step = Step.REQUEST_CALCULATE_SALT; if (this.certHelper.cert.meta.salt) { return this.sendError('Certificate already has a salt: ' + this.certHelper.cert.meta.salt); } // store server generated salt in certificate this.certHelper.cert.meta.salt = submitResult[0].salt; let serverSaltedHash = submitResult[0].salted_document_hash; let saltedHash = await this.certHelper.calculateSaltedHash(); if (serverSaltedHash != saltedHash) { return this.sendError('Locally calculated salted_hash does not match salted_hash from server: ' + serverSaltedHash + ' != ' + saltedHash); } this.certHelper.cert.info.salted_hash = saltedHash; this.sendMessage('Calculated salted hash matching server: ' + saltedHash, MessageType.SUCCESS); this.step = Step.REQUEST_RECEIVE_PRECERT; let startTime = new Date(); let precertAvailable = false; let precertResult; this.certHelper.cert.blockchains = {}; while (Date.now() - startTime.getTime() < this.config.CERT_REQUEST_TIMEOUT * 1000) { const pollingNeeded = this.certHelper.cert.meta.blockchains.some((blockchain) => !Object.keys(this.certHelper.cert.blockchains).includes(blockchain) && Math.floor(Date.now() / 1000) > estimatedAvailabilityTimes[blockchain]); if (!pollingNeeded) { // Check, if process was cancelled here, since no message might be sent for a long time if (this.cancelled) { throw new ProcessError('Aborted by user by calling cancel()', ErrorTypes.UNEXPECTED); } await sleep_ms(100); continue; } let precertResponse = await getRequest(`/v${this.certHelper.cert.meta.version}/precert/`, { salted_document_hash: saltedHash }, this.config); //TODO: validate structure precertResult = await precertResponse.json(); if (precertResponse.status === 200) { //wait until all expected blockchains are present in the certificate let unhandledBlockchains = Object.keys(precertResult.blockchains).filter((blockchainName) => !(blockchainName in this.certHelper.cert.blockchains)); for (let blockchain of unhandledBlockchains) { this.currentBlockchain = blockchain; //Start subprocess for verification this.step = Step.REQUEST_CREATE_CERTIFICATE_FOR_SINGLE_BLOCKCHAIN; this.certHelper.cert.blockchains[blockchain] = precertResult.blockchains[blockchain]; let blockchainVerificationProcess = new VerificationProcess(this.certHelper.cert, this.inputData); blockchainVerificationProcess.skipBlockchains = this.certHelper.cert.meta.blockchains.filter((blockchainToSkip) => blockchainToSkip !== blockchain); let verifyResult = await blockchainVerificationProcess .configure(this.config) .onMessage((data) => { if (data.type != MessageType.ERROR) { // errors are handled using verifyResult, so we can also abort the request this.sendMessage(`[Verification for ${blockchain} | ${Step[data.step]}] ${data.message}`, data.step == Step.VERIFICATION_ALL_BLOCKCHAINS_SUCCEEDED ? MessageType.SUCCESS : MessageType.PROGRESS, data.additionalDataType, data.additionalData); } }) .start(); if (verifyResult.step !== Step.VERIFICATION_ALL_BLOCKCHAINS_SUCCEEDED) { return this.sendError('[VerificationError|' + Step[verifyResult.step] + '] ' + verifyResult.message, verifyResult.additionalDataType, verifyResult.additionalData); } } // done with blockchain specific verification this.currentBlockchain = undefined; if (this.certHelper.cert.meta.blockchains.every((blockchain) => blockchain in precertResult.blockchains)) { this.sendMessage('Retrieved Precert: ' + saltedHash, MessageType.SUCCESS, AdditionalDataType.HTTP_RESPONSE_JSON, precertResult); precertAvailable = true; break; } else { // wait before retrying await sleep_s(this.config.CERT_REQUEST_INTERVAL); this.step = Step.REQUEST_RECEIVE_PRECERT; continue; } } else if (precertResponse.status === 202) { this.sendMessage('Precert not available, with message from API: ' + precertResult.detail, MessageType.PROGRESS, AdditionalDataType.HTTP_RESPONSE_JSON, precertResult); } else { return this.sendError('HTTP request for Precert failed with unexpected status code: ' + precertResponse.status, AdditionalDataType.HTTP_RESPONSE_JSON, precertResult); } // wait before retrying await sleep_s(this.config.CERT_REQUEST_INTERVAL); } // no precert means a timeout occured if (!precertAvailable) { return this.sendError('Timeout for precert request after ' + (Date.now() - startTime.getTime()) / 1000 + ' seconds.'); } // fullcert only has type success and means everything is done this.step = Step.REQUEST_FULL_CERTIFICATE_SUCCEEDED; return this.sendMessage('Certificate created and verified successfully!', MessageType.SUCCESS); } catch (e) { return this.handleException(e); } } } //# sourceMappingURL=request.js.map