UNPKG

@ceramicnetwork/core

Version:

Typescript implementation of the Ceramic protocol

182 lines • 10.8 kB
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