UNPKG

@cronstamp/clientlib

Version:

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

270 lines 12 kB
import { hashFile, hashString, isValidHash, VersionToHashAlgorithm } from '../util/hash.js'; import { Log } from '../util/logger.js'; import { CertificateHelper } from '../certificate.js'; import { DefaultConfig, updateConfig } from '../util/config.js'; import { blockchainNames } from '../blockchains/blockchains.js'; export var Step; (function (Step) { // Used in both Request and Verification Step[Step["INIT"] = 0] = "INIT"; Step[Step["HASH_INPUT_DATA"] = 1] = "HASH_INPUT_DATA"; // Exclusive to Request Step[Step["REQUEST_CALCULATE_SALT"] = 2] = "REQUEST_CALCULATE_SALT"; Step[Step["REQUEST_SUBMIT_DOCUMENT_HASH"] = 3] = "REQUEST_SUBMIT_DOCUMENT_HASH"; Step[Step["REQUEST_RECEIVE_PRECERT"] = 4] = "REQUEST_RECEIVE_PRECERT"; Step[Step["REQUEST_CREATE_CERTIFICATE_FOR_SINGLE_BLOCKCHAIN"] = 5] = "REQUEST_CREATE_CERTIFICATE_FOR_SINGLE_BLOCKCHAIN"; Step[Step["REQUEST_FULL_CERTIFICATE_SUCCEEDED"] = 6] = "REQUEST_FULL_CERTIFICATE_SUCCEEDED"; // Exclusive to Verification Step[Step["VERIFICATION_CHECK_CERTIFICATE_SYNTAX"] = 7] = "VERIFICATION_CHECK_CERTIFICATE_SYNTAX"; Step[Step["VERIFICATION_COMPARE_SALTED_HASHES"] = 8] = "VERIFICATION_COMPARE_SALTED_HASHES"; Step[Step["VERIFICATION_VERIFY_SINGLE_BLOCKCHAIN"] = 9] = "VERIFICATION_VERIFY_SINGLE_BLOCKCHAIN"; Step[Step["VERIFICATION_ALL_BLOCKCHAINS_SUCCEEDED"] = 10] = "VERIFICATION_ALL_BLOCKCHAINS_SUCCEEDED"; // this only has type success and means a full certificate is verified })(Step || (Step = {})); export var MessageType; (function (MessageType) { MessageType[MessageType["SUCCESS"] = 0] = "SUCCESS"; MessageType[MessageType["ERROR"] = 1] = "ERROR"; MessageType[MessageType["PROGRESS"] = 2] = "PROGRESS"; // intermediate step (e.g. polling from api) })(MessageType || (MessageType = {})); export var AdditionalDataType; (function (AdditionalDataType) { AdditionalDataType[AdditionalDataType["NONE"] = 0] = "NONE"; AdditionalDataType[AdditionalDataType["PRECERT_URL"] = 1] = "PRECERT_URL"; AdditionalDataType[AdditionalDataType["HTTP_RESPONSE_JSON"] = 2] = "HTTP_RESPONSE_JSON"; AdditionalDataType[AdditionalDataType["ERROR"] = 3] = "ERROR"; //TODO: remove ERROR_INFO, // information on an error AdditionalDataType[AdditionalDataType["HASH"] = 4] = "HASH"; AdditionalDataType[AdditionalDataType["TIMESTAMP"] = 5] = "TIMESTAMP"; AdditionalDataType[AdditionalDataType["PARSED_CERTIFICATE"] = 6] = "PARSED_CERTIFICATE"; AdditionalDataType[AdditionalDataType["INVALID_SYNTAX_CERTIFICATE"] = 7] = "INVALID_SYNTAX_CERTIFICATE"; AdditionalDataType[AdditionalDataType["PROCESSED_BLOCKCHAIN"] = 8] = "PROCESSED_BLOCKCHAIN"; // used, when a request or validation process has added and verified the specified blockchain })(AdditionalDataType || (AdditionalDataType = {})); export var InputDataType; (function (InputDataType) { InputDataType[InputDataType["STRING"] = 0] = "STRING"; InputDataType[InputDataType["FILE"] = 1] = "FILE"; InputDataType[InputDataType["HASH"] = 2] = "HASH"; })(InputDataType || (InputDataType = {})); export var ErrorTypes; (function (ErrorTypes) { ErrorTypes[ErrorTypes["DOCUMENT_ALTERED"] = 0] = "DOCUMENT_ALTERED"; ErrorTypes[ErrorTypes["TEMPORARY"] = 1] = "TEMPORARY"; ErrorTypes[ErrorTypes["UNEXPECTED"] = 2] = "UNEXPECTED"; })(ErrorTypes || (ErrorTypes = {})); export class CertificateProcess { config; inputData; certHelper; messageHandler; errorHandler; step = Step.INIT; currentBlockchain = undefined; started = false; cancelled = false; log; constructor(data, moduleName) { this.inputData = data; this.config = DefaultConfig; this.log = new Log(DefaultConfig.LOG_LEVEL, moduleName); } onMessage(handler) { this.messageHandler = handler; return this; } onError(handler) { this.errorHandler = handler; return this; } cancel() { this.cancelled = true; } configure(newConfig) { if (this.started) { this.sendError('Attemped calling configure() function after process was started using start()!'); } else { this.config = updateConfig(this.config, newConfig, this.log); } return this; } /** * start process by handling input data * @returns last result data containing a success or error message */ async start() { this.step = Step.INIT; if (this.started) { return this.sendError('The same process can not be started twice: '); } this.started = true; try { this.step = Step.HASH_INPUT_DATA; let document_hash; switch (this.inputData.type) { case InputDataType.STRING: { const message = this.inputData.data; document_hash = await hashString(message); this.sendMessage('Hash for "' + message + '" is ' + document_hash, MessageType.PROGRESS); break; } case InputDataType.FILE: { const file = this.inputData.data; document_hash = await hashFile(file); this.sendMessage('Hash for file "' + file.name + '" is ' + document_hash, MessageType.PROGRESS); break; } case InputDataType.HASH: { document_hash = this.inputData.data; this.sendMessage('Directly using input as hash: ' + document_hash, MessageType.PROGRESS); break; } } // throws a process error, if the hash is invalid isValidHash(document_hash); let version = 1; // use provided subset of blockchains or default to using all available blockchains, if none are provided let blockchainsToRequire = blockchainNames; if (this.inputData.blockchainsToRequire) { if (!CertificateHelper.isValidBlockchainList(this.inputData.blockchainsToRequire)) { return this.sendError('Invalid blockchain requested in list: ' + this.inputData.blockchainsToRequire + ' Valid are: ' + blockchainNames); } // deduplicate and sort list of required blockchains blockchainsToRequire = [...new Set(this.inputData.blockchainsToRequire)].sort(); } if (blockchainsToRequire.length === 0) { return this.sendError('At least one blockchain has to be selected for certificate creation. Valid blockchains are: ' + blockchainNames); } //create certificate with only document_hash, version and blockchains this.certHelper = new CertificateHelper({ blockchains: undefined, meta: { version: version, blockchains: blockchainsToRequire, salt: undefined }, info: { hash_algorithm: VersionToHashAlgorithm[version], salted_hash: undefined }, document_hash: document_hash }); // done with preparation return this.sendMessage('Generated hash: ' + document_hash, MessageType.SUCCESS); } catch (e) { return this.handleException(e); } } sendError(message, additionalDataType = AdditionalDataType.NONE, additionalData = undefined) { return this.sendMessage(message, MessageType.ERROR, additionalDataType, additionalData); } sendMessage(message, type, additionalDataType = AdditionalDataType.NONE, additionalData = undefined) { const resultData = { step: this.step, type: type, blockchain: this.currentBlockchain, currentCert: structuredClone(this.certHelper?.cert), //do not let the user modify the internal certificate object message: message, additionalDataType: additionalDataType, additionalData: additionalData, inputData: { ...this.inputData, certFileName: this.getInputDataName().substring(0, 20), displayName: this.getShortName() }, currentTime: new Date() }; if (this.cancelled) { throw new ProcessError('Aborted by user by calling cancel()', ErrorTypes.UNEXPECTED); //This is not redirected to the user error handler } const blockchainStr = resultData.blockchain ? '|' + resultData.blockchain : ''; const tagStr = `[${Step[this.step]}|${MessageType[type]}${blockchainStr}][${this.getIdentifier()}]`; if (type == MessageType.ERROR) { this.log.err(`${tagStr} Error: "${message}" at step `, resultData); } else { this.log.debug(`${tagStr} Message: "${message}" at step ${Step[this.step]}`, resultData); } if (type == MessageType.ERROR && this.errorHandler) { this.errorHandler(resultData); } else if (this.messageHandler) { if (this.messageHandler(resultData) === false) { //Abort triggered in message handler this.cancel(); throw new ProcessError('Message handler aborted process!', ErrorTypes.UNEXPECTED, additionalDataType, resultData); } } else { this.log.warn('No message handler set for certificate process. Did not send message:', resultData); } return resultData; } handleException(exception) { if (!this.cancelled) { if (exception instanceof ProcessError) { return this.sendError(exception.message, AdditionalDataType.ERROR, exception.errorInfo); } else { //print directly, to make clickable in console this.log.err('', exception); return this.sendError('Unexpected exception occurred: ' + exception.message, AdditionalDataType.ERROR, { rawError: exception, reason: exception.cause, type: ErrorTypes.UNEXPECTED, additionalDataType: AdditionalDataType.NONE }); } } } getIdentifier() { return this.getInputDataName().substring(0, 10); } getShortName() { const maxLength = 15; let text = this.getInputDataName(); if (this.inputData.type == InputDataType.FILE) { let dotPos = text.lastIndexOf('.'); if (dotPos >= 0) { if (dotPos > maxLength) { return text.substring(0, maxLength) + '[...]' + text.substring(dotPos); } else { return text; } } } if (text.length < maxLength) { return text; } return text.substring(0, maxLength) + '[...]'; } getInputDataName() { switch (this.inputData.type) { case InputDataType.HASH: case InputDataType.STRING: return this.inputData.data; case InputDataType.FILE: return this.inputData.data.name; } } } export class ProcessError extends Error { additionalData; errorInfo; constructor(message, type, additionalDataType = AdditionalDataType.NONE, additionalData = undefined) { super(); this.errorInfo = { rawError: this, type: type, additionalDataType: additionalDataType, additionalData: additionalData }; this.message = message; } } //# sourceMappingURL=manager.js.map