UNPKG

@ceramicnetwork/common

Version:
237 lines • 9.24 kB
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