UNPKG

@ethersphere/bee-js

Version:
136 lines 6.49 kB
import { Binary, Optional, Types } from 'cafe-utility'; import { asContentAddressedChunk } from "../chunk/cac.js"; import { makeSingleOwnerChunkFromData, uploadSingleOwnerChunkData, uploadSingleOwnerChunkWithWrappedChunk } from "../chunk/soc.js"; import * as bytes from "../modules/bytes.js"; import * as chunkAPI from "../modules/chunk.js"; import { fetchLatestFeedUpdate, probeFeed } from "../modules/feed.js"; import { Bytes } from "../utils/bytes.js"; import { BeeResponseError } from "../utils/error.js"; import { ResourceLocator } from "../utils/resource-locator.js"; import { FeedIndex, Identifier, Reference, Signature } from "../utils/typed-bytes.js"; import { makeFeedIdentifier } from "./identifier.js"; const TIMESTAMP_PAYLOAD_OFFSET = 0; const TIMESTAMP_PAYLOAD_SIZE = 8; const REFERENCE_PAYLOAD_OFFSET = TIMESTAMP_PAYLOAD_SIZE; export async function findNextIndex(requestOptions, owner, topic) { try { const feedUpdate = await fetchLatestFeedUpdate(requestOptions, owner, topic); if (!feedUpdate.feedIndexNext) { throw Error('Feed index next is not defined. This should happen when fetching an exact index.'); } return feedUpdate.feedIndexNext; } catch (e) { if (e instanceof BeeResponseError) { return FeedIndex.fromBigInt(0n); } throw e; } } export async function updateFeedWithReference(requestOptions, signer, topic, reference, postageBatchId, options) { reference = new Reference(reference); const nextIndex = options?.index ?? (await findNextIndex(requestOptions, signer.publicKey().address(), topic)); const identifier = makeFeedIdentifier(topic, nextIndex); const at = options?.at ?? Date.now() / 1000.0; const timestamp = Binary.numberToUint64(BigInt(Math.floor(at)), 'BE'); const payloadBytes = Binary.concatBytes(timestamp, reference.toUint8Array()); return uploadSingleOwnerChunkData(requestOptions, signer, postageBatchId, identifier, payloadBytes, options); } export async function updateFeedWithPayload(requestOptions, signer, topic, data, postageBatchId, options) { const nextIndex = options?.index ?? (await findNextIndex(requestOptions, signer.publicKey().address(), topic)); const identifier = makeFeedIdentifier(topic, nextIndex); if (data.length > 4096) { const uploadResult = await bytes.upload(requestOptions, data, postageBatchId, options); const rootChunk = await chunkAPI.download(requestOptions, uploadResult.reference); return uploadSingleOwnerChunkWithWrappedChunk(requestOptions, signer, postageBatchId, identifier, rootChunk, options); } return uploadSingleOwnerChunkData(requestOptions, signer, postageBatchId, identifier, Types.isString(data) ? Bytes.fromUtf8(data).toUint8Array() : data, options); } export function getFeedUpdateChunkReference(owner, topic, index) { const identifier = makeFeedIdentifier(topic, index); return new Reference(Binary.keccak256(Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()))); } export async function downloadFeedUpdate(requestOptions, owner, topic, index, hasTimestamp = false) { index = typeof index === 'number' ? FeedIndex.fromBigInt(BigInt(index)) : index; const address = getFeedUpdateChunkReference(owner, topic, index); const data = await chunkAPI.download(requestOptions, address.toHex()); const soc = makeSingleOwnerChunkFromData(data, address); let timestamp = Optional.empty(); if (hasTimestamp) { const timestampBytes = Bytes.fromSlice(soc.payload.toUint8Array(), TIMESTAMP_PAYLOAD_OFFSET, TIMESTAMP_PAYLOAD_SIZE); timestamp = Optional.of(Number(Binary.uint64ToNumber(timestampBytes.toUint8Array(), 'BE'))); } return { timestamp, payload: new Bytes(soc.payload.offset(hasTimestamp ? REFERENCE_PAYLOAD_OFFSET : 0)) }; } export async function downloadFeedUpdateAsCAC(requestOptions, owner, topic, index) { index = typeof index === 'number' ? FeedIndex.fromBigInt(BigInt(index)) : index; const address = getFeedUpdateChunkReference(owner, topic, index); const data = await chunkAPI.download(requestOptions, address); return asContentAddressedChunk(data.slice(Identifier.LENGTH + Signature.LENGTH)); } export function makeFeedReader(requestOptions, topic, owner) { // TODO: remove after enough time has passed in deprecated version const download = async options => { if (options?.index === undefined) { return fetchLatestFeedUpdate(requestOptions, owner, topic); } const update = await downloadFeedUpdate(requestOptions, owner, topic, options.index, options.hasTimestamp ?? true); const feedIndex = typeof options.index === 'number' ? FeedIndex.fromBigInt(BigInt(options.index)) : options.index; return { payload: update.payload, feedIndex, feedIndexNext: feedIndex.next() }; }; const downloadPayload = async options => { if (options?.index === undefined) { return fetchLatestFeedUpdate(requestOptions, owner, topic); } const cac = await downloadFeedUpdateAsCAC(requestOptions, owner, topic, options.index); const payload = cac.span.toBigInt() <= 4096n ? cac.payload : await bytes.download(requestOptions, new ResourceLocator(cac.address)); const feedIndex = typeof options.index === 'number' ? FeedIndex.fromBigInt(BigInt(options.index)) : options.index; return { payload, feedIndex, feedIndexNext: feedIndex.next() }; }; const downloadReference = async options => { let index = options?.index; if (index === undefined) { index = (await probeFeed(requestOptions, owner, topic)).feedIndex; } const payload = await download({ ...options, index: index }); return { reference: new Reference(payload.payload.toUint8Array()), feedIndex: payload.feedIndex, feedIndexNext: payload.feedIndexNext ?? payload.feedIndex.next() }; }; return { download, downloadPayload, downloadReference, owner, topic }; } export function makeFeedWriter(requestOptions, topic, signer) { const upload = async (postageBatchId, reference, options) => { return updateFeedWithReference(requestOptions, signer, topic, reference, postageBatchId, options); }; const uploadPayload = async (postageBatchId, data, options) => { return updateFeedWithPayload(requestOptions, signer, topic, data, postageBatchId, options); }; return { ...makeFeedReader(requestOptions, topic, signer.publicKey().address()), upload, uploadReference: upload, uploadPayload }; }