@ceramicnetwork/core
Version:
Typescript implementation of the Ceramic protocol
182 lines • 10.8 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _RemoteCAS_logger, _RemoteCAS_requestsApiEndpoint, _RemoteCAS_chainIdApiEndpoint, _RemoteCAS_sendRequest, _RemoteCAS_stopSignal, _RemoteCAS_versionInfo, _RemoteCAS_numFailedRequests, _RemoteCAS_firstFailedRequestDate;
import { AnchorRequestStatusName } from '@ceramicnetwork/common';
import { CASResponseOrError, ErrorResponse, SupportedChainsResponse } from '@ceramicnetwork/codecs';
import { validate, isValid, decode } from 'codeco';
import { deferAbortable } from '../../ancillary/defer-abortable.js';
import { catchError, firstValueFrom, Subject, takeUntil } from 'rxjs';
import { ServiceMetrics as Metrics } from '@ceramicnetwork/observability';
const MAX_FAILED_REQUESTS = 3;
const MAX_MILLIS_WITHOUT_SUCCESS = 1000 * 60;
const CAS_REQUEST_CREATED = 'cas_request_created';
const CAS_REQUEST_POLLED = 'cas_request_polled';
const CAS_REQUEST_FAILED = 'cas_request_failed';
const CAS_REQUEST_CREATE_FAILED = 'cas_request_create_failed';
const CAS_REQUEST_POLL_FAILED = 'cas_request_poll_failed';
const CAS_REQUEST_COMPLETED = 'cas_request_completed';
const CAS_INACCESSIBLE = 'cas_inaccessible';
const CAS_CREATE_REQUEST_TIME = 'cas_create_request_time';
const CAS_POLL_STATUS_TIME = 'cas_poll_status_time';
function parseResponse(streamId, tip, json) {
const validation = validate(CASResponseOrError, json);
if (!isValid(validation)) {
return {
status: AnchorRequestStatusName.FAILED,
streamId: streamId,
cid: tip,
message: `Unexpected response from CAS: ${JSON.stringify(json)}`,
};
}
const parsed = validation.right;
if (ErrorResponse.is(parsed)) {
Metrics.count(CAS_REQUEST_FAILED, 1);
return {
status: AnchorRequestStatusName.FAILED,
streamId: streamId,
cid: tip,
message: parsed.error,
};
}
else {
if (parsed.status === AnchorRequestStatusName.COMPLETED) {
Metrics.count(CAS_REQUEST_COMPLETED, 1);
return {
status: parsed.status,
streamId: parsed.streamId,
cid: parsed.cid,
message: parsed.message,
witnessCar: parsed.witnessCar,
};
}
return {
status: parsed.status,
streamId: parsed.streamId,
cid: parsed.cid,
message: parsed.message,
};
}
}
export class RemoteCAS {
constructor(logger, anchorServiceUrl, sendRequest, versionInfo) {
_RemoteCAS_logger.set(this, void 0);
_RemoteCAS_requestsApiEndpoint.set(this, void 0);
_RemoteCAS_chainIdApiEndpoint.set(this, void 0);
_RemoteCAS_sendRequest.set(this, void 0);
_RemoteCAS_stopSignal.set(this, void 0);
_RemoteCAS_versionInfo.set(this, void 0);
_RemoteCAS_numFailedRequests.set(this, void 0);
_RemoteCAS_firstFailedRequestDate.set(this, void 0);
__classPrivateFieldSet(this, _RemoteCAS_logger, logger, "f");
__classPrivateFieldSet(this, _RemoteCAS_requestsApiEndpoint, anchorServiceUrl + '/api/v0/requests', "f");
__classPrivateFieldSet(this, _RemoteCAS_chainIdApiEndpoint, anchorServiceUrl + '/api/v0/service-info/supported_chains', "f");
__classPrivateFieldSet(this, _RemoteCAS_sendRequest, sendRequest, "f");
__classPrivateFieldSet(this, _RemoteCAS_versionInfo, versionInfo, "f");
__classPrivateFieldSet(this, _RemoteCAS_stopSignal, new Subject(), "f");
__classPrivateFieldSet(this, _RemoteCAS_numFailedRequests, 0, "f");
__classPrivateFieldSet(this, _RemoteCAS_firstFailedRequestDate, null, "f");
}
assertCASAccessible() {
if (__classPrivateFieldGet(this, _RemoteCAS_numFailedRequests, "f") < MAX_FAILED_REQUESTS) {
return;
}
const now = new Date();
const millisSinceFirstFailure = now.getTime() - __classPrivateFieldGet(this, _RemoteCAS_firstFailedRequestDate, "f").getTime();
if (millisSinceFirstFailure > MAX_MILLIS_WITHOUT_SUCCESS) {
const err = new Error(`Ceramic Anchor Service appears to be inaccessible. We have failed to contact the CAS ${__classPrivateFieldGet(this, _RemoteCAS_numFailedRequests, "f")} times in a row, starting at ${__classPrivateFieldGet(this, _RemoteCAS_firstFailedRequestDate, "f").toISOString()}. Note that failure to anchor may cause data loss.`);
__classPrivateFieldGet(this, _RemoteCAS_logger, "f").err(err);
Metrics.count(CAS_INACCESSIBLE, 1);
throw err;
}
}
_recordCASContactFailure() {
var _a;
if (__classPrivateFieldGet(this, _RemoteCAS_numFailedRequests, "f") === 0) {
__classPrivateFieldSet(this, _RemoteCAS_firstFailedRequestDate, new Date(), "f");
}
__classPrivateFieldSet(this, _RemoteCAS_numFailedRequests, (_a = __classPrivateFieldGet(this, _RemoteCAS_numFailedRequests, "f"), _a++, _a), "f");
}
_recordCASContactSuccess(action) {
if (__classPrivateFieldGet(this, _RemoteCAS_numFailedRequests, "f") >= MAX_FAILED_REQUESTS) {
__classPrivateFieldGet(this, _RemoteCAS_logger, "f").imp(`Successfully ${action} a request against the CAS`);
}
__classPrivateFieldSet(this, _RemoteCAS_numFailedRequests, 0, "f");
__classPrivateFieldSet(this, _RemoteCAS_firstFailedRequestDate, null, "f");
}
async supportedChains() {
const response = await __classPrivateFieldGet(this, _RemoteCAS_sendRequest, "f").call(this, __classPrivateFieldGet(this, _RemoteCAS_chainIdApiEndpoint, "f"));
try {
const supportedChainsResponse = decode(SupportedChainsResponse, response);
return supportedChainsResponse.supportedChains;
}
catch (error) {
throw new Error(`SupportedChains response : ${JSON.stringify(response)} does not contain contain the field <supportedChains> or is of size more than 1: ${error}`);
}
}
async createRequest(streamId, tip, timestamp) {
const response = await firstValueFrom(this.create$(streamId, tip, timestamp));
return parseResponse(streamId, tip, response);
}
create$(streamId, tip, timestamp) {
const sendRequest$ = deferAbortable(async (signal) => {
const timeStart = Date.now();
const response = await __classPrivateFieldGet(this, _RemoteCAS_sendRequest, "f").call(this, __classPrivateFieldGet(this, _RemoteCAS_requestsApiEndpoint, "f"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
streamId: streamId.toString(),
cid: tip.toString(),
timestamp: timestamp.toISOString(),
jsCeramicVersion: __classPrivateFieldGet(this, _RemoteCAS_versionInfo, "f").cliPackageVersion,
ceramicOneVersion: __classPrivateFieldGet(this, _RemoteCAS_versionInfo, "f").ceramicOneVersion,
},
signal: signal,
});
const timeEnd = Date.now();
Metrics.observe(CAS_CREATE_REQUEST_TIME, timeEnd - timeStart);
this._recordCASContactSuccess('created');
Metrics.count(CAS_REQUEST_CREATED, 1);
return response;
});
return sendRequest$.pipe(catchError((error) => {
this._recordCASContactFailure();
Metrics.count(CAS_REQUEST_CREATE_FAILED, 1);
throw new Error(`Error connecting to CAS while attempting to anchor ${streamId} at commit ${tip}. This is failure #${__classPrivateFieldGet(this, _RemoteCAS_numFailedRequests, "f")} attempting to communicate to the CAS: ${error.message}`);
}), takeUntil(__classPrivateFieldGet(this, _RemoteCAS_stopSignal, "f")));
}
async getStatusForRequest(streamId, tip) {
const requestUrl = [__classPrivateFieldGet(this, _RemoteCAS_requestsApiEndpoint, "f"), tip.toString()].join('/');
const timeStart = Date.now();
const sendRequest$ = deferAbortable(async (signal) => {
const response = await __classPrivateFieldGet(this, _RemoteCAS_sendRequest, "f").call(this, requestUrl, { signal: signal });
this._recordCASContactSuccess('polled');
Metrics.count(CAS_REQUEST_POLLED, 1);
const timeEnd = Date.now();
Metrics.observe(CAS_POLL_STATUS_TIME, timeEnd - timeStart);
return response;
});
const response = await firstValueFrom(sendRequest$.pipe(catchError((error) => {
this._recordCASContactFailure();
Metrics.count(CAS_REQUEST_POLL_FAILED, 1);
throw new Error(`Error connecting to CAS while attempting to poll the status of request for StreamID ${streamId} at commit ${tip}. This is failure #${__classPrivateFieldGet(this, _RemoteCAS_numFailedRequests, "f")} attempting to communicate to the CAS: ${error.message}`);
}), takeUntil(__classPrivateFieldGet(this, _RemoteCAS_stopSignal, "f"))));
return parseResponse(streamId, tip, response);
}
async close() {
__classPrivateFieldGet(this, _RemoteCAS_stopSignal, "f").next();
__classPrivateFieldGet(this, _RemoteCAS_stopSignal, "f").complete();
}
}
_RemoteCAS_logger = new WeakMap(), _RemoteCAS_requestsApiEndpoint = new WeakMap(), _RemoteCAS_chainIdApiEndpoint = new WeakMap(), _RemoteCAS_sendRequest = new WeakMap(), _RemoteCAS_stopSignal = new WeakMap(), _RemoteCAS_versionInfo = new WeakMap(), _RemoteCAS_numFailedRequests = new WeakMap(), _RemoteCAS_firstFailedRequestDate = new WeakMap();
//# sourceMappingURL=remote-cas.js.map