UNPKG

@tangle-js/anchors

Version:

Anchoring messages to the Tangle. Powered by IOTA Streams

310 lines 20.1 kB
import { AnchoringChannelError } from "./errors/anchoringChannelError.mjs"; import { AnchoringChannelErrorNames } from "./errors/anchoringChannelErrorNames.mjs"; import { ClientHelper } from "./helpers/clientHelper.mjs"; import { SeedHelper } from "./helpers/seedHelper.mjs"; import ValidationHelper from "./helpers/validationHelper.mjs"; import AnchorMsgService from "./services/anchorMsgService.mjs"; import ChannelService from "./services/channelService.mjs"; import FetchMsgService from "./services/fetchMsgService.mjs"; export class IotaAnchoringChannel { constructor(channelID, nodeInfo, isPrivate, encrypted) { this._node = nodeInfo; this._channelID = channelID; const components = channelID.split(":"); this._channelAddress = components[0]; this._announceMsgID = components[1]; if (isPrivate) { this._keyLoadMsgID = components[2]; } this._encrypted = encrypted; this._isPrivate = isPrivate; } /** * Returns the channelID ('channelAddress:announce_msg_id') * * @returns channel ID */ get channelID() { return this._channelID; } /** * Returns the channel's address * * @returns channel address */ get channelAddr() { return this._channelAddress; } /** * Returns the channel's first anchorage ID * * @returns anchorageID */ get firstAnchorageID() { let result = this._keyLoadMsgID; if (!result) { result = this._announceMsgID; } return result; } /** * Returns the channel's node * * @returns node */ get node() { return this._node.node; } /** * Returns the channel's publisher seed * * @returns seed */ get seed() { return this._seed; } /** * Returns the channel's author Public Key * * @returns the Author's Public key */ get authorPubKey() { return this._authorPubKey; } /** * Returns the channel's subscriber Public Key * * @returns the subscriber's Public key */ get subscriberPubKey() { return this._subscriberPubKey; } /** * Returns whether the channel is encrypted or not * * @returns boolean */ get encrypted() { return this._encrypted; } /** * Returns whether the channel is private or not * * @returns boolean */ get isPrivate() { return this._isPrivate; } /** * Creates a new Anchoring Channel * * @param seed Author's seed * @param options The options * @param options.node The node used to create the channel * @returns The anchoring channel details */ static async create(seed, options) { if (options?.node && !ValidationHelper.url(options?.node)) { throw new AnchoringChannelError(AnchoringChannelErrorNames.INVALID_NODE, "The node has to be a URL"); } const node = options?.node; const permanode = options?.permanode; let encrypted = false; let isPrivate = false; // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (options?.encrypted === true) { encrypted = true; } // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (options?.isPrivate === true) { isPrivate = true; } if (!isPrivate && options?.presharedKeys) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_BINDING_ERROR, "Pre-shared keys are only for Private Channels"); } const client = await this.getClient(node, permanode); const { channelAddress, announceMsgID, keyLoadMsgID, authorPk } = await ChannelService.createChannel(client, seed, isPrivate, options?.presharedKeys); let firstAnchorageID = announceMsgID; if (keyLoadMsgID) { firstAnchorageID = keyLoadMsgID; } const details = { channelAddr: channelAddress, channelID: `${channelAddress}:${announceMsgID}${keyLoadMsgID ? `:${keyLoadMsgID}` : ""}`, firstAnchorageID, authorPubKey: authorPk, authorSeed: seed, node: node || this.DEFAULT_NODE, encrypted, isPrivate }; return details; } /** * Instantiates an existing Anchoring Channel from a Channel ID * * @param channelID in the form of 'channel_address:announce_msg_id' * @param options Channel options * @returns reference to the channel */ static fromID(channelID, options) { const components = channelID.split(":"); let encrypted = false; // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (options?.encrypted === true) { encrypted = true; } let isPrivate = false; // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (options?.isPrivate === true) { isPrivate = true; } if (Array.isArray(components) && ((components.length === 2 && !isPrivate) || (components.length === 3 && isPrivate))) { let node = options?.node; const permanode = options?.permanode; if (!node) { node = this.DEFAULT_NODE; } return new IotaAnchoringChannel(channelID, { node, permanode }, isPrivate, encrypted); } throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_BINDING_ERROR, `Invalid channel identifier: ${channelID}`); } /** * Creates a new IotaAnchoringChannel and subscribes to it using the Author's seed * * i.e. Author === Subscriber * A new Seed is automatically generated * * @param options The channel creation options * @returns The Anchoring Channel */ static async bindNew(options) { const details = await IotaAnchoringChannel.create(SeedHelper.generateSeed(), options); let opts = options; if (!opts) { opts = {}; } return IotaAnchoringChannel.fromID(details.channelID, opts).bind(details.authorSeed); } static async getClient(node, permanode) { let client; if (!node && !permanode) { client = await ClientHelper.getMainnetClient(); } else if (!node) { client = await ClientHelper.getClient(ClientHelper.DEFAULT_NODE, permanode); } else { client = await ClientHelper.getClient(node, permanode); } return client; } /** * Binds the channel so that the subscriber is instantiated using the seed passed as parameter * * @param seed The Subscriber (publisher) seed * @param psk The Subscriber preshared key * @returns a Reference to the channel */ async bind(seed, psk) { if (this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_ALREADY_BOUND, `Channel already bound to ${this._channelID}`); } this._seed = seed; const client = await IotaAnchoringChannel.getClient(this._node.node, this._node.permanode); const bindRequest = { client, seed: this._seed, isPrivate: this._isPrivate, presharedKey: psk, encrypted: this._encrypted, channelID: this._channelID }; // The author's PK for the time being is not set because cannot be obtained from the // announce message const { subscriber, authorPk } = await ChannelService.bindToChannel(bindRequest); this._authorPubKey = authorPk; this._subscriber = subscriber; this._subscriberPubKey = subscriber.get_public_key(); return this; } /** * Anchors a message to the anchoring channel * * @param message Message to be anchored * @param anchorageID The anchorage to be used * @returns The result of the operation */ async anchor(message, anchorageID) { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } const request = { channelID: this._channelID, encrypted: this._encrypted, isPrivate: this._isPrivate, subscriber: this._subscriber, message, anchorageID }; const result = await AnchorMsgService.anchor(request); return result; } /** * Fetches a previously anchored message * * @param anchorageID The anchorage point * @param messageID The expected ID of the anchored message * @returns The fetch result */ async fetch(anchorageID, messageID) { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } const request = { channelID: this._channelID, encrypted: this._encrypted, isPrivate: this._isPrivate, subscriber: this._subscriber, msgID: messageID, anchorageID }; return FetchMsgService.fetch(request); } /** * Fetches the next message anchored to the channel * * @returns The fetch result or undefined if no more messages can be fetched */ async fetchNext() { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } return FetchMsgService.fetchNext(this._subscriber, this._encrypted); } /** * Receives a previously anchored message * provided its anchorage has already been seen on the channel * * @param messageID The ID of the message * @param anchorageID The expected ID of message's anchorage * @returns The message received and associated metadata */ async receive(messageID, anchorageID) { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } const request = { channelID: this._channelID, encrypted: this._encrypted, isPrivate: this._isPrivate, subscriber: this._subscriber, msgID: messageID, anchorageID }; return FetchMsgService.receive(request); } } IotaAnchoringChannel.DEFAULT_NODE = ClientHelper.DEFAULT_NODE; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW90YUFuY2hvcmluZ0NoYW5uZWwubWpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2lvdGFBbmNob3JpbmdDaGFubmVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ3ZFLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDbEQsT0FBTyxnQkFBZ0IsTUFBTSw0QkFBNEIsQ0FBQztBQVUxRCxPQUFPLGdCQUFnQixNQUFNLDZCQUE2QixDQUFDO0FBQzNELE9BQU8sY0FBYyxNQUFNLDJCQUEyQixDQUFDO0FBQ3ZELE9BQU8sZUFBZSxNQUFNLDRCQUE0QixDQUFDO0FBRXpELE1BQU0sT0FBTyxvQkFBb0I7SUF5QjdCLFlBQW9CLFNBQWlCLEVBQUUsUUFBbUIsRUFBRSxTQUFrQixFQUFFLFNBQWtCO1FBQzlGLElBQUksQ0FBQyxLQUFLLEdBQUcsUUFBUSxDQUFDO1FBRXRCLElBQUksQ0FBQyxVQUFVLEdBQUcsU0FBUyxDQUFDO1FBRTVCLE1BQU0sVUFBVSxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFeEMsSUFBSSxDQUFDLGVBQWUsR0FBRyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDckMsSUFBSSxDQUFDLGNBQWMsR0FBRyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFcEMsSUFBSSxTQUFTLEVBQUU7WUFDWCxJQUFJLENBQUMsYUFBYSxHQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQztTQUN0QztRQUVELElBQUksQ0FBQyxVQUFVLEdBQUcsU0FBUyxDQUFDO1FBQzVCLElBQUksQ0FBQyxVQUFVLEdBQUcsU0FBUyxDQUFDO0lB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