@ethersphere/bee-js
Version:
Javascript client for Bee
136 lines • 6.49 kB
JavaScript
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
};
}