@ceramicnetwork/common
Version:
Ceramic common types and utilities
237 lines • 9.24 kB
JavaScript
import cloneDeep from 'lodash.clonedeep';
import * as uint8arrays from 'uint8arrays';
import { toCID } from './cid-utils.js';
import { AnchorStatus, } from '../stream.js';
import { EnvironmentUtils } from '../utils/environment-utils.js';
import { CommitID, StreamID, StreamType } from '@ceramicnetwork/streamid';
import { CID } from 'multiformats/cid';
import { base64urlToJSON } from './uint8array-utils.js';
const TILE_TYPE_ID = 0;
export class StreamUtils {
static streamIdFromState(state) {
return new StreamID(state.type, state.log[0].cid);
}
static tipFromState(state) {
return state.log[state.log.length - 1].cid;
}
static serializeCommit(commit) {
const cloned = cloneDeep(commit);
if (StreamUtils.isSignedCommitContainer(cloned)) {
cloned.jws.link = cloned.jws.link.toString();
cloned.linkedBlock = uint8arrays.toString(cloned.linkedBlock, 'base64');
if (cloned.cacaoBlock) {
cloned.cacaoBlock = uint8arrays.toString(cloned.cacaoBlock, 'base64');
}
return cloned;
}
if (StreamUtils.isSignedCommit(commit)) {
cloned.link = cloned.link.toString();
}
if (StreamUtils.isAnchorCommit(commit)) {
cloned.proof = cloned.proof.toString();
}
if (cloned.id) {
cloned.id = cloned.id.toString();
}
if (cloned.prev) {
cloned.prev = cloned.prev.toString();
}
if (commit.header?.model) {
cloned.header.model = uint8arrays.toString(commit.header.model, 'base64');
}
if (commit.header?.context) {
cloned.header.context = uint8arrays.toString(commit.header.context, 'base64');
}
if (commit.header?.unique) {
cloned.header.unique = uint8arrays.toString(commit.header.unique, 'base64');
}
return cloned;
}
static deserializeCommit(commit) {
const cloned = cloneDeep(commit);
if (StreamUtils.isSignedCommitContainer(cloned)) {
cloned.jws.link = toCID(cloned.jws.link);
cloned.linkedBlock = uint8arrays.fromString(cloned.linkedBlock, 'base64');
if (cloned.cacaoBlock) {
cloned.cacaoBlock = uint8arrays.fromString(cloned.cacaoBlock, 'base64');
}
return cloned;
}
if (StreamUtils.isSignedCommit(cloned)) {
cloned.link = toCID(cloned.link);
}
if (StreamUtils.isAnchorCommit(cloned)) {
cloned.proof = toCID(cloned.proof);
}
if (cloned.id) {
cloned.id = toCID(cloned.id);
}
if (cloned.prev) {
cloned.prev = toCID(cloned.prev);
}
if (cloned.header?.model) {
cloned.header.model = uint8arrays.fromString(cloned.header.model, 'base64');
}
if (cloned.header?.context) {
cloned.header.context = uint8arrays.fromString(cloned.header.context, 'base64');
}
if (cloned.header?.unique) {
cloned.header.unique = uint8arrays.fromString(cloned.header.unique, 'base64');
}
return cloned;
}
static serializeState(state) {
const cloned = cloneDeep(state);
cloned.log = cloned.log.map((entry) => ({ ...entry, cid: entry.cid.toString() }));
if (cloned.anchorStatus != null) {
cloned.anchorStatus = AnchorStatus[cloned.anchorStatus];
}
if (cloned.anchorProof != null) {
cloned.anchorProof.txHash = cloned.anchorProof.txHash.toString();
cloned.anchorProof.root = cloned.anchorProof.root.toString();
}
if (state.metadata?.model) {
cloned.metadata.model = state.metadata.model.toString();
}
if (state.metadata?.context) {
cloned.metadata.context = state.metadata.context.toString();
}
if (state.metadata?.unique && state.type != TILE_TYPE_ID) {
cloned.metadata.unique = uint8arrays.toString(Uint8Array.from(state.metadata.unique), 'base64');
}
cloned.doctype = StreamType.nameByCode(cloned.type);
return cloned;
}
static deserializeState(state) {
if (!state)
return null;
const cloned = cloneDeep(state);
if (cloned.doctype) {
cloned.type = StreamType.codeByName(cloned.doctype);
delete cloned.doctype;
}
cloned.log = cloned.log.map((entry) => ({ ...entry, cid: toCID(entry.cid) }));
if (cloned.anchorProof) {
cloned.anchorProof.txHash = toCID(cloned.anchorProof.txHash);
cloned.anchorProof.root = toCID(cloned.anchorProof.root);
}
if (cloned.anchorStatus) {
cloned.anchorStatus = AnchorStatus[cloned.anchorStatus];
}
if (state.metadata?.model) {
cloned.metadata.model = StreamID.fromString(state.metadata.model);
}
if (state.metadata?.context) {
cloned.metadata.context = StreamID.fromString(state.metadata.context);
}
if (state.metadata?.unique && state.type != TILE_TYPE_ID) {
cloned.metadata.unique = uint8arrays.fromString(state.metadata.unique, 'base64');
}
return cloned;
}
static statesEqual(state1, state2) {
return (JSON.stringify(StreamUtils.serializeState(state1)) ===
JSON.stringify(StreamUtils.serializeState(state2)));
}
static isStateSupersetOf(state, base) {
if (state.log.length < base.log.length) {
return false;
}
for (const i in base.log) {
if (!state.log[i].cid.equals(base.log[i].cid)) {
return false;
}
}
if (state.log.length === base.log.length && state.anchorStatus != base.anchorStatus) {
return false;
}
return true;
}
static assertCommitLinksToState(state, commit) {
const streamId = this.streamIdFromState(state);
if (commit.id && !commit.id.equals(state.log[0].cid)) {
throw new Error(`Invalid genesis CID in commit for StreamID ${streamId.toString()}. Found: ${commit.id}, expected ${state.log[0].cid}`);
}
const expectedPrev = state.log[state.log.length - 1].cid;
if (!commit.prev.equals(expectedPrev)) {
throw new Error(`Commit doesn't properly point to previous commit in log. Expected ${expectedPrev}, found 'prev' ${commit.prev}`);
}
}
static async convertCommitToSignedCommitContainer(commit, ipfs) {
if (StreamUtils.isSignedCommit(commit)) {
const block = await ipfs.block.get(toCID(commit.link), {
offline: EnvironmentUtils.useRustCeramic(),
});
return {
jws: commit,
linkedBlock: block,
};
}
return commit;
}
static isSignedCommitContainer(commit) {
return commit && commit.jws !== undefined;
}
static isSignedCommit(commit) {
return commit && commit.link !== undefined;
}
static getCacaoCidFromCommit(commit) {
if (StreamUtils.isSignedCommit(commit)) {
const decodedProtectedHeader = base64urlToJSON(commit.signatures[0].protected);
if (decodedProtectedHeader.cap) {
const capIPFSUri = decodedProtectedHeader.cap;
return CID.parse(capIPFSUri.replace('ipfs://', ''));
}
}
return undefined;
}
static isAnchorCommit(commit) {
return commit && commit.proof !== undefined;
}
static isSignedCommitData(commitData) {
return commitData && commitData.envelope !== undefined;
}
static isAnchorCommitData(commitData) {
return commitData && commitData.proof !== undefined;
}
static commitDataToLogEntry(commitData, eventType) {
const logEntry = {
cid: commitData.cid,
type: eventType,
};
if (commitData?.capability?.p?.exp) {
logEntry.expirationTime = Math.floor(Date.parse(commitData.capability.p.exp) / 1000);
}
if (commitData.timestamp) {
logEntry.timestamp = commitData.timestamp;
}
return logEntry;
}
static anchorTimestampFromState(state) {
for (let i = state.log.length - 1; i >= 0; i--) {
const entry = state.log[i];
if (entry.timestamp) {
return entry.timestamp;
}
}
return null;
}
static validDIDString(did) {
if (typeof did != 'string') {
return false;
}
if (!did.startsWith('did:')) {
return false;
}
return true;
}
static stateContainsCommit(state, commit) {
return state.log.find((logEntry) => logEntry.cid.equals(commit)) != null;
}
static commitIdFromStreamState(streamState) {
const tipCID = streamState.log[streamState.log.length - 1].cid;
const genesisCID = streamState.log[0].cid;
return new CommitID(streamState.type, genesisCID, tipCID);
}
}
//# sourceMappingURL=stream-utils.js.map