UNPKG

@tangle.js/anchors

Version:

Anchoring messages to the Tangle. Powered by IOTA Streams

324 lines (275 loc) 9.92 kB
import { Subscriber } from "@tangle.js/iota_streams_wasm"; import { AnchoringChannelError } from "./errors/anchoringChannelError"; import { AnchoringChannelErrorNames } from "./errors/anchoringChannelErrorNames"; import initialize from "./helpers/initializationHelper"; import { SeedHelper } from "./helpers/seedHelper"; import ValidationHelper from "./helpers/validationHelper"; import { IAnchoringRequest } from "./models/IAnchoringRequest"; import { IAnchoringResult } from "./models/IAnchoringResult"; import { IBindChannelRequest } from "./models/IBindChannelRequest"; import { IChannelDetails } from "./models/IChannelDetails"; import { IChannelOptions } from "./models/IChannelOptions"; import { IFetchRequest } from "./models/IFetchRequest"; import { IFetchResult } from "./models/IFetchResult"; import AnchorMsgService from "./services/anchorMsgService"; import ChannelService from "./services/channelService"; import FetchMsgService from "./services/fetchMsgService"; // Needed for the Streams WASM bindings initialize(); export class IotaAnchoringChannel { public static readonly DEFAULT_NODE = "https://chrysalis-nodes.iota.org"; private readonly _channelID: string; private readonly _node: string; private _seed: string; private readonly _channelAddress: string; private readonly _announceMsgID: string; private _subscriber: Subscriber; private readonly _authorPubKey: string; private _subscriberPubKey: string; // authorPubKey param will disappear in the future private constructor(channelAddr: string, announceMsgID: string, node: string, authorPubKey: string) { this._node = node; this._channelID = `${channelAddr}:${announceMsgID}`; this._channelAddress = channelAddr; this._announceMsgID = announceMsgID; this._authorPubKey = authorPubKey; } /** * 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 */ public static async create(seed: string, options?: IChannelOptions): Promise<IChannelDetails> { if (options?.node && !ValidationHelper.url(options?.node)) { throw new AnchoringChannelError(AnchoringChannelErrorNames.INVALID_NODE, "The node has to be a URL"); } let node = options?.node; if (!node) { node = this.DEFAULT_NODE; } const { channelAddress, announceMsgID, authorPk } = await ChannelService.createChannel(node, seed); const details: IChannelDetails = { channelAddr: channelAddress, channelID: `${channelAddress}:${announceMsgID}`, firstAnchorageID: announceMsgID, authorPubKey: authorPk, authorSeed: seed, node }; 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 * */ public static fromID(channelID: string, options?: IChannelOptions): IotaAnchoringChannel { const components: string[] = channelID.split(":"); if (Array.isArray(components) && components.length === 2) { let node = options?.node; if (!node) { node = this.DEFAULT_NODE; } const authorPubKey = options?.authorPubKey; return new IotaAnchoringChannel(components[0], components[1], node, authorPubKey); } 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 */ public static async bindNew(options?: IChannelOptions): Promise<IotaAnchoringChannel> { const details = await IotaAnchoringChannel.create(SeedHelper.generateSeed(), options); // Temporarily until Streams exposed it on the Subscriber let opts = options; if (!opts) { opts = {}; } opts.authorPubKey = details.authorPubKey; return IotaAnchoringChannel.fromID(details.channelID, opts).bind(details.authorSeed); } /** * Binds the channel so that the subscriber is instantiated using the seed passed as parameter * * @param seed The Subscriber (publisher) seed * @returns a Reference to the channel * */ public async bind(seed: string): Promise<IotaAnchoringChannel> { if (this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_ALREADY_BOUND, `Channel already bound to ${this._channelID}`); } this._seed = seed; const bindRequest: IBindChannelRequest = { node: this._node, seed: this._seed, channelID: this._channelID }; // The author's PK for the time being is not set because cannot be obtained from the // announce message const { subscriber } = await ChannelService.bindToChannel(bindRequest); this._subscriber = subscriber; // this._authorPk = authorPk; this._subscriberPubKey = subscriber.get_public_key(); return this; } /** * Returns the channelID ('channelAddress:announce_msg_id') * * @returns channel ID * */ public get channelID(): string { return this._channelID; } /** * Returns the channel's address * * @returns channel address * */ public get channelAddr(): string { return this._channelAddress; } /** * Returns the channel's first anchorage ID * * @returns anchorageID * */ public get firstAnchorageID(): string { return this._announceMsgID; } /** * Returns the channel's node * * @returns node * */ public get node(): string { return this._node; } /** * Returns the channel's publisher seed * * @returns seed * */ public get seed(): string { return this._seed; } /** * Returns the channel's author Public Key * * @returns the Author's Public key * */ public get authorPubKey(): string { return this._authorPubKey; } /** * Returns the channel's publisher Public Key * * @returns the publisher's Public key * */ public get subscriberPubKey(): string { return this._subscriberPubKey; } /** * 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 * */ public async anchor(message: Buffer, anchorageID: string): Promise<IAnchoringResult> { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } const request: IAnchoringRequest = { channelID: this._channelID, 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 */ public async fetch(anchorageID: string, messageID?: string): Promise<IFetchResult> { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } const request: IFetchRequest = { channelID: this._channelID, 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 */ public async fetchNext(): Promise<IFetchResult | undefined> { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } return FetchMsgService.fetchNext(this._subscriber); } /** * 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 */ public async receive(messageID: string, anchorageID?: string): Promise<IFetchResult> { if (!this._subscriber) { throw new AnchoringChannelError(AnchoringChannelErrorNames.CHANNEL_NOT_BOUND, "Unbound anchoring channel. Please call bind first"); } const request: IFetchRequest = { channelID: this._channelID, subscriber: this._subscriber, msgID: messageID, anchorageID }; return FetchMsgService.receive(request); } }