@cronstamp/clientlib
Version:
Client library for cronstamp, a blockchain-based document timestamping and verification service.
176 lines • 9.33 kB
JavaScript
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