import { NativeModules, Platform, NativeEventEmitter} from 'react-native'; import {decode, encode} from 'base-64' const LINKING_ERROR = `The package '@waku/react-native' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo managed workflow\n'; const ReactNative = NativeModules.ReactNative ? NativeModules.ReactNative : new Proxy( {}, { get() { throw new Error(LINKING_ERROR); }, } ); export function multiply(a: number, b: number): Promise { return ReactNative.multiply(a, b); } export class WakuMessage { payload: Uint8Array = new Uint8Array(); contentTopic: String | null = ""; version: Number | null = 0; timestamp: Number | null = null; toJSON(){ const b64encoded = encode(String.fromCharCode(...this.payload)); return { contentTopic: this.contentTopic, version: this.version, timestamp: this.timestamp, payload: b64encoded } } } var eventEmitter = new NativeEventEmitter(NativeModules.ReactNative); /** * Execute function each time a message is received * @param cb callback to be eecuted */ export function onMessage(cb: (arg0:any) => void) { eventEmitter.addListener("message", event => { let signal = JSON.parse(event.signal); let msg = signal.event.wakuMessage; signal.event.wakuMessage = new WakuMessage(); signal.event.wakuMessage.timestamp = msg.timestamp; signal.event.wakuMessage.version = msg.version || 0; signal.event.wakuMessage.contentTopic = msg.contentTopic; signal.event.wakuMessage.payload = new Uint8Array(decode(msg.payload ?? []).split("").map((c:any) => c.charCodeAt(0))); cb(signal.event); }) } export class Config { host: String | null = null port: Number | null = null advertiseAddr: String | null = null nodeKey: String | null = null keepAliveInterval: Number | null = null relay: Boolean | null = null filter: Boolean | null = null minPeersToPublish: Number | null = null } /** * Instantiates a Waku node. * @param config options used to initialize a go-waku node */ export function newNode(config: Config | null): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.newNode(config ? JSON.stringify(config) : "")); if(response.error){ reject(response.error); } else { resolve(); } }); } /** * Start a Waku node mounting all the protocols that were enabled during the Waku node instantiation. */ export function start(): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.start()); if(response.error){ reject(response.error); } else { resolve(); } }); } /** * Stops a Waku node. */ export function stop(): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.stop()); if(response.error){ reject(response.error); } else { resolve(); } }); } /** * Is the node started? * @returns `true` if the node is started, `false` otherwise */ export function isStarted(): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.isStarted()); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Get the peer ID of the waku node. * @returns Base58 encoded peer ID */ export function peerID(): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.peerID()); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Publish a message using Waku Relay. * @param msg WakuMessage to publish. The message version is overwritten to `0` * @param pubsubTopic pubsub topic on which to publish the message. If not specified, it will use the default pubsub topic * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned * @returns string containing the message id */ export function relayPublish(msg: WakuMessage, pubsubTopic: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg) let response = JSON.parse(await ReactNative.relayPublish(messageJSON, pubsubTopic, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Optionally sign, encrypt using asymmetric encryption and publish a message using Waku Relay. * @param msg WakuMessage to publish. The message version is overwritten to `1` * @param publicKey hex encoded public key to be used for encryption. * @param optionalSigningKey hex encoded private key to be used to sign the message. * @param pubsubTopic pubsub topic on which to publish the message. If not specified, it will use the default pubsub topic. * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned * @returns string containing the message id */ export function relayPublishEncodeAsymmetric(msg: WakuMessage, publicKey: String, optionalSigningKey: String = "", pubsubTopic: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg) let response = JSON.parse(await ReactNative.relayPublishEncodeAsymmetric(messageJSON, pubsubTopic, publicKey, optionalSigningKey, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Optionally sign, encrypt using symmetric encryption and publish a message using Waku Relay. * @param msg WakuMessage to publish. The message version is overwritten to `1` * @param symmetricKey 32 byte hex encoded secret key to be used for encryption * @param optionalSigningKey hex encoded private key to be used to sign the message * @param pubsubTopic pubsub topic on which to publish the message. If not specified, it will use the default pubsub topic * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned * @returns string containing the message id */ export function relayPublishEncodeSymmetric(msg: WakuMessage, symmetricKey: String, optionalSigningKey: String = "", pubsubTopic: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg) let response = JSON.parse(await ReactNative.relayPublishEncodeAsymmetric(messageJSON, pubsubTopic, symmetricKey, optionalSigningKey, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Subscribe to a Waku Relay pubsub topic to receive messages. * @param topic Pubsub topic to subscribe to. */ export function relaySubscribe(topic: String = ""): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.relaySubscribe(topic)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Returns the default pubsub topic used for exchanging waku messages defined in [RFC 10](https://rfc.vac.dev/spec/10/). * @returns the default pubsub topic `/waku/2/default-waku/proto` */ export function defaultPubsubTopic(): Promise { return ReactNative.defaultPubsubTopic(); } /** * Get the multiaddresses the Waku node is listening to. * @returns an array of multiaddresses */ export function listenAddresses(): Promise> { return new Promise>(async (resolve, reject) => { let response = JSON.parse(await ReactNative.listenAddresses()); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Add a node multiaddress and protocol to the waku node's peerstore. * @param multiAddress multiaddress (with peer id) to reach the peer being added * @param protocol protocol we expect the peer to support * @returns peer ID as a base58 `string` of the peer that was added */ export function addPeer(multiAddress: String, protocol: String): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.addPeer(multiAddress, protocol)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Dial peer using a multiaddress. * @param multiAddress multiaddress to reach the peer being dialed * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned */ export function connect(multiAddress: String, timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.connect(multiAddress, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(); } }); } /** * Dial peer using its peer ID. * @param peerID Peer ID to dial. The peer must be already known. It must have been added before with `addPeer` or previously dialed with `connect` * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned */ export function connectPeerID(peerID: String, timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.connectPeerID(peerID, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(); } }); } /** * Disconnect a peer using its peerID * @param peerID Peer ID to disconnect. */ export function disconnect(peerID: String): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.disconnect(peerID)); if(response.error){ reject(response.error); } else { resolve(); } }); } /** * Get number of connected peers. * @returns number of connected peers */ export function peerCnt(): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.peerCnt()); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } export class DecodedPayload { payload: Uint8Array = new Uint8Array(); padding: Uint8Array = new Uint8Array(); pubkey: String | null = ""; signature: String | null = ""; toJSON(){ const b64payload = encode(String.fromCharCode(...this.payload)); const b64padding = encode(String.fromCharCode(...this.padding)); return { payload: b64payload, padding: b64padding, pubkey: this.pubkey, signature: this.signature, } } } /** * Decrypt a message using a symmetric key * @param msg WakuMessage to decode. The message version is expected to be 1 * @param symmetricKey 32 byte symmetric key hex encoded * @returns DecodedPayload */ export function decodeSymmetric(msg: WakuMessage, symmetricKey: String): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg); let response = JSON.parse(await ReactNative.decodeSymmetric(messageJSON, symmetricKey)); if(response.error){ reject(response.error); } else { let decodedPayload = new DecodedPayload(); decodedPayload.payload = new Uint8Array(atob(response.result.payload).split("").map(c => c.charCodeAt(0))); decodedPayload.padding = new Uint8Array(atob(response.result.padding).split("").map(c => c.charCodeAt(0))); decodedPayload.pubkey = response.result.pubkey; decodedPayload.signature = response.result.signature; resolve(decodedPayload); } }); } /** * Decrypt a message using a secp256k1 private key * @param msg WakuMessage to decode. The message version is expected to be 1 * @param privateKey secp256k1 private key hex encoded * @returns DecodedPayload */ export function decodeAsymmetric(msg: WakuMessage, privateKey: String): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg); let response = JSON.parse(await ReactNative.decodeSymmetric(messageJSON, privateKey)); if(response.error){ reject(response.error); } else { let decodedPayload = new DecodedPayload(); decodedPayload.payload = new Uint8Array(atob(response.result.payload).split("").map(c => c.charCodeAt(0))); decodedPayload.padding = new Uint8Array(atob(response.result.padding).split("").map(c => c.charCodeAt(0))); decodedPayload.pubkey = response.result.pubkey; decodedPayload.signature = response.result.signature; resolve(decodedPayload); } }); } /** * Determine if there are enough peers to publish a message on a given pubsub topic. * @param pubsubTopic Pubsub topic to verify. If not specified, it will verify the default pubsub topic * @returns boolean indicates whether there are enough peers */ export function relayEnoughPeers(pubsubTopic: String = ""): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.relayEnoughPeers(pubsubTopic)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Closes the pubsub subscription to a pubsub topic. No more messages will be received from this pubsub topic. * @param pubsubTopic */ export function relayUnsubscribe(pubsubTopic: String = ""): Promise { return new Promise(async (resolve, reject) => { let response = JSON.parse(await ReactNative.relayUnsubscribe(pubsubTopic)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Publish a message using Waku Lightpush. * @param msg WakuMessage to publish. The message version is overwritten to `0` * @param pubsubTopic pubsub topic on which to publish the message. If not specified, it uses the default pubsub topic. * @param peerID Peer ID supporting the lightpush protocol. The peer must be already known. It must have been added before with `addPeer` or previously dialed with `connect` * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned * @returns the message ID */ export function lightpushPublish(msg: WakuMessage, pubsubTopic: String = "", peerID: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg) let response = JSON.parse(await ReactNative.lightpushPublish(messageJSON, pubsubTopic, peerID, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Optionally sign, encrypt using asymmetric encryption and publish a message using Waku Lightpush. * @param msg WakuMessage to publish. The message version is overwritten to `1` * @param publicKey hex encoded public key to be used for encryption * @param optionalSigningKey hex encoded private key to be used to sign the message * @param pubsubTopic pubsub topic on which to publish the message. If not specified, it uses the default pubsub topic. * @param peerID Peer ID supporting the lightpush protocol. The peer must be already known. It must have been added before with `addPeer` or previously dialed with `connect` * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned * @returns the message ID */ export function lightpushPublishEncAsymmetric(msg: WakuMessage, publicKey: String, optionalSigningKey: String = "", pubsubTopic: String = "", peerID: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg) let response = JSON.parse(await ReactNative.lightpushPublishEncodeAsymmetric(messageJSON, pubsubTopic, peerID, publicKey, optionalSigningKey, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } /** * Optionally sign, encrypt using symmetric encryption and publish a message using Waku Lightpush. * @param msg WakuMessage to publish. The message version is overwritten to `1` * @param symmetricKey hex encoded secret key to be used for encryption. * @param optionalSigningKey hex encoded private key to be used to sign the message. * @param pubsubTopic pubsub topic on which to publish the message. If not specified, it uses the default pubsub topic. * @param peerID Peer ID supporting the lightpush protocol. The peer must be already known. It must have been added before with `addPeer` or previously dialed with `connect` * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned * @returns the message ID */ export function lightpushPublishEncSymmetric(msg: WakuMessage, symmetricKey: String, optionalSigningKey: String = "", pubsubTopic: String = "", peerID: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let messageJSON = JSON.stringify(msg) let response = JSON.parse(await ReactNative.lightpushPublishEncodeAsymmetric(messageJSON, pubsubTopic, peerID, symmetricKey, optionalSigningKey, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } export class Peer { addrs: Array = Array() connected: Boolean = false peerID: String = "" protocols: Array = Array() constructor(addrs: Array, connected: Boolean, peerID: String, protocols: Array){ this.addrs = addrs; this.connected = connected; this.peerID = peerID; this.protocols = protocols; } } /** * Retrieve the list of peers known by the Waku node. * @returns list of peers */ export function peers(): Promise> { return new Promise>(async (resolve, reject) => { let response = JSON.parse(await ReactNative.peers()); if(response.error){ reject(response.error); } else { resolve(response.result.map((x:any) => new Peer(x.addrs, x.connected, x.peerID, x.protocols))); } }) } export class Index { digest: Uint8Array = new Uint8Array(); receiverTime: Number = 0 senderTime: Number = 0 pubsubTopic: String = "" } export class PagingOptions { pageSize: Number = 0 cursor: Index | null = null forward: Boolean = false constructor(pageSize: Number = 0, forward: Boolean = false, cursor: Index | null = null){ this.pageSize = pageSize this.forward = forward this.cursor = cursor } } export class ContentFilter { contentTopic: String = "" constructor(contentTopic: String = "") { this.contentTopic = contentTopic } } export class StoreQuery { pubsubTopic: String | null = null contentFilters: Array = Array() startTime: Number = 0 endTime: Number = 0 pagingOptions: PagingOptions | null = null constructor(pubsubTopic: String | null = null, contentFilters: Array = Array(), startTime: Number = 0, endTime: Number = 0, pagingOptions: PagingOptions | null = null) { this.pubsubTopic = pubsubTopic this.contentFilters = contentFilters this.startTime = startTime this.endTime = endTime this.pagingOptions = pagingOptions } } /** * * @param query * @param peerID * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned * @returns */ export function storeQuery(query: StoreQuery, peerID: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let queryJSON = JSON.stringify(query) let response = JSON.parse(await ReactNative.storeQuery(queryJSON, peerID, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(response.result); } }); } export class FilterSubscription { pubsubTopic: String | null = null contentFilters: Array = Array() constructor(pubsubTopic: String | null = null, contentFilters: Array = Array()) { this.pubsubTopic = pubsubTopic this.contentFilters = contentFilters } } /** * * @param filter * @param peerID * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned */ export function filterSubscribe(filter: FilterSubscription, peerID: String = "", timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let filterJSON = JSON.stringify(filter) let response = JSON.parse(await ReactNative.filterSubscribe(filterJSON, peerID, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(); } }); } /** * * @param filter * @param timeoutMs Timeout value in milliseconds to execute the call. If the function takes longer than this value, the execution will be canceled and an error returned */ export function filterUnsubscribe(filter: FilterSubscription, timeoutMs: Number = 0): Promise { return new Promise(async (resolve, reject) => { let filterJSON = JSON.stringify(filter) let response = JSON.parse(await ReactNative.filterUnsubscribe(filterJSON, timeoutMs)); if(response.error){ reject(response.error); } else { resolve(); } }); }