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