2021-01-19 15:47:49 +03:00
|
|
|
import { FluenceClient } from './FluenceClient';
|
|
|
|
import { SecurityTetraplet } from './internal/commonTypes';
|
2021-02-02 14:13:52 +03:00
|
|
|
import { Particle } from './internal/particle';
|
2021-01-19 15:47:49 +03:00
|
|
|
import Multiaddr from 'multiaddr';
|
|
|
|
import PeerId, { isPeerId } from 'peer-id';
|
|
|
|
import { generatePeerId, seedToPeerId } from './internal/peerIdUtils';
|
2021-01-29 16:48:27 +03:00
|
|
|
import { FluenceClientImpl } from './internal/FluenceClientImpl';
|
2021-02-24 14:51:24 +03:00
|
|
|
import log from 'loglevel';
|
2021-01-19 15:47:49 +03:00
|
|
|
|
|
|
|
type Node = {
|
|
|
|
peerId: string;
|
|
|
|
multiaddr: string;
|
|
|
|
};
|
|
|
|
|
2021-01-29 16:48:27 +03:00
|
|
|
/**
|
|
|
|
* Creates a Fluence client. If the `connectTo` is specified connects the client to the network
|
|
|
|
* @param { string | Multiaddr | Node } [connectTo] - Node in Fluence network to connect to. If not specified client will not be connected to the n
|
|
|
|
* @param { PeerId | string } [peerIdOrSeed] - The Peer Id of the created client. Specified either as PeerId structure or as seed string. Will be generated randomly if not specified
|
|
|
|
* @returns { Promise<FluenceClient> } Promise which will be resolved with the created FluenceClient
|
|
|
|
*/
|
2021-01-19 15:47:49 +03:00
|
|
|
export const createClient = async (
|
|
|
|
connectTo?: string | Multiaddr | Node,
|
|
|
|
peerIdOrSeed?: PeerId | string,
|
|
|
|
): Promise<FluenceClient> => {
|
|
|
|
let peerId;
|
|
|
|
if (!peerIdOrSeed) {
|
|
|
|
peerId = await generatePeerId();
|
|
|
|
} else if (isPeerId(peerIdOrSeed)) {
|
|
|
|
// keep unchanged
|
|
|
|
peerId = peerIdOrSeed;
|
|
|
|
} else {
|
|
|
|
// peerId is string, therefore seed
|
|
|
|
peerId = await seedToPeerId(peerIdOrSeed);
|
|
|
|
}
|
|
|
|
|
2021-01-29 16:48:27 +03:00
|
|
|
const client = new FluenceClientImpl(peerId);
|
2021-01-19 15:47:49 +03:00
|
|
|
|
|
|
|
if (connectTo) {
|
|
|
|
let theAddress: Multiaddr;
|
|
|
|
let fromNode = (connectTo as any).multiaddr;
|
|
|
|
if (fromNode) {
|
|
|
|
theAddress = new Multiaddr(fromNode);
|
|
|
|
} else {
|
|
|
|
theAddress = new Multiaddr(connectTo as string);
|
|
|
|
}
|
|
|
|
|
|
|
|
await client.connect(theAddress);
|
2021-02-24 14:51:24 +03:00
|
|
|
if (!(await checkConnection(client))) {
|
|
|
|
throw new Error('Connection check failed. Check if the node is working or try to connect to another node');
|
2021-02-19 16:32:02 +03:00
|
|
|
}
|
2021-01-19 15:47:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return client;
|
|
|
|
};
|
|
|
|
|
2021-01-29 16:48:27 +03:00
|
|
|
/**
|
|
|
|
* Send a particle to Fluence Network using the specified Fluence Client.
|
|
|
|
* @param { FluenceClient } client - The Fluence Client instance.
|
|
|
|
* @param { Particle } particle - The particle to send.
|
|
|
|
*/
|
2021-01-19 15:47:49 +03:00
|
|
|
export const sendParticle = async (client: FluenceClient, particle: Particle): Promise<string> => {
|
|
|
|
return await client.sendScript(particle.script, particle.data, particle.ttl);
|
|
|
|
};
|
|
|
|
|
2021-01-29 16:48:27 +03:00
|
|
|
/**
|
|
|
|
* Registers a function which can be called on the client from Aquamarine. The registration is per client basis.
|
|
|
|
* @param { FluenceClient } client - The Fluence Client instance.
|
|
|
|
* @param { string } serviceId - The identifier of service which would be used to make calls from Aquamarine
|
|
|
|
* @param { string } fnName - The identifier of function which would be used to make calls from Aquamarine
|
|
|
|
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => object } handler - The handler which would be called by Aquamarine infrastructure. The result is any object passed back to Aquamarine
|
|
|
|
*/
|
2021-01-19 15:47:49 +03:00
|
|
|
export const registerServiceFunction = (
|
|
|
|
client: FluenceClient,
|
|
|
|
serviceId: string,
|
|
|
|
fnName: string,
|
|
|
|
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => object,
|
|
|
|
) => {
|
2021-01-29 16:48:27 +03:00
|
|
|
(client as FluenceClientImpl).registerCallback(serviceId, fnName, handler);
|
2021-01-19 15:47:49 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// prettier-ignore
|
2021-01-29 16:48:27 +03:00
|
|
|
/**
|
|
|
|
* Removes registers for the function previously registered with {@link registerServiceFunction}
|
|
|
|
* @param { FluenceClient } client - The Fluence Client instance.
|
|
|
|
* @param { string } serviceId - The identifier of service used in {@link registerServiceFunction} call
|
|
|
|
* @param { string } fnName - The identifier of function used in {@link registerServiceFunction} call
|
|
|
|
*/
|
2021-01-19 15:47:49 +03:00
|
|
|
export const unregisterServiceFunction = (
|
|
|
|
client: FluenceClient,
|
|
|
|
serviceId: string,
|
|
|
|
fnName: string
|
|
|
|
) => {
|
2021-01-29 16:48:27 +03:00
|
|
|
(client as FluenceClientImpl).unregisterCallback(serviceId, fnName);
|
2021-01-19 15:47:49 +03:00
|
|
|
};
|
|
|
|
|
2021-01-29 16:48:27 +03:00
|
|
|
/**
|
|
|
|
* Registers an event-like handler for all calls to the specific service\function pair from from Aquamarine. The registration is per client basis. Return a function which when called removes the subscription.
|
|
|
|
* Same as registerServiceFunction which immediately returns empty object.
|
|
|
|
* @param { FluenceClient } client - The Fluence Client instance.
|
|
|
|
* @param { string } serviceId - The identifier of service calls to which from Aquamarine are transformed into events.
|
|
|
|
* @param { string } fnName - The identifier of function calls to which from Aquamarine are transformed into events.
|
|
|
|
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => object } handler - The handler which would be called by Aquamarine infrastructure
|
|
|
|
* @returns { Function } - A function which when called removes the subscription.
|
|
|
|
*/
|
2021-01-19 15:47:49 +03:00
|
|
|
export const subscribeToEvent = (
|
|
|
|
client: FluenceClient,
|
|
|
|
serviceId: string,
|
|
|
|
fnName: string,
|
|
|
|
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => void,
|
|
|
|
): Function => {
|
|
|
|
const realHandler = (args: any[], tetraplets: SecurityTetraplet[][]) => {
|
|
|
|
// dont' block
|
2021-02-02 14:13:52 +03:00
|
|
|
setTimeout(() => {
|
2021-01-19 15:47:49 +03:00
|
|
|
handler(args, tetraplets);
|
2021-02-02 14:13:52 +03:00
|
|
|
}, 0);
|
2021-01-19 15:47:49 +03:00
|
|
|
|
|
|
|
return {};
|
|
|
|
};
|
|
|
|
registerServiceFunction(client, serviceId, fnName, realHandler);
|
|
|
|
return () => {
|
|
|
|
unregisterServiceFunction(client, serviceId, fnName);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-01-29 16:48:27 +03:00
|
|
|
/**
|
|
|
|
* Send a particle with a fetch-like semantics. In order to for this to work you have to you have to make a call to the same callbackServiceId\callbackFnName pair from Air script as specified by the parameters. The arguments of the call are returned as the resolve value of promise
|
|
|
|
* @param { FluenceClient } client - The Fluence Client instance.
|
|
|
|
* @param { Particle } particle - The particle to send.
|
|
|
|
* @param { string } callbackFnName - The identifier of function which should be used in Air script to pass the data to fetch "promise"
|
|
|
|
* @param { [string]='_callback' } callbackServiceId - The service identifier which should be used in Air script to pass the data to fetch "promise"
|
|
|
|
* @returns { Promise<T> } - A promise which would be resolved with the data returned from Aquamarine
|
|
|
|
*/
|
2021-01-19 15:47:49 +03:00
|
|
|
export const sendParticleAsFetch = async <T>(
|
|
|
|
client: FluenceClient,
|
|
|
|
particle: Particle,
|
|
|
|
callbackFnName: string,
|
|
|
|
callbackServiceId: string = '_callback',
|
|
|
|
): Promise<T> => {
|
|
|
|
const serviceId = callbackServiceId;
|
|
|
|
const fnName = callbackFnName;
|
|
|
|
|
|
|
|
let promise: Promise<T> = new Promise(function (resolve, reject) {
|
|
|
|
const unsub = subscribeToEvent(client, serviceId, fnName, (args: any[], _) => {
|
|
|
|
unsub();
|
|
|
|
resolve(args as any);
|
|
|
|
});
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
unsub();
|
|
|
|
reject(new Error(`callback for ${callbackServiceId}/${callbackFnName} timed out after ${particle.ttl}`));
|
|
|
|
}, particle.ttl);
|
|
|
|
});
|
|
|
|
|
2021-02-19 16:32:02 +03:00
|
|
|
await sendParticle(client, particle);
|
2021-01-19 15:47:49 +03:00
|
|
|
|
|
|
|
return promise;
|
|
|
|
};
|
2021-02-19 16:32:02 +03:00
|
|
|
|
|
|
|
export const checkConnection = async (client: FluenceClient): Promise<boolean> => {
|
|
|
|
let msg = Math.random().toString(36).substring(7);
|
2021-02-24 14:51:24 +03:00
|
|
|
let callbackFn = 'checkConnection';
|
|
|
|
let callbackService = '_callback';
|
2021-02-19 16:32:02 +03:00
|
|
|
|
|
|
|
const particle = new Particle(
|
|
|
|
`
|
|
|
|
(seq
|
|
|
|
(call __relay ("op" "identity") [msg] result)
|
|
|
|
(call myPeerId ("${callbackService}" "${callbackFn}") [result])
|
|
|
|
)
|
|
|
|
`,
|
|
|
|
{
|
|
|
|
__relay: client.relayPeerId,
|
|
|
|
myPeerId: client.selfPeerId,
|
2021-02-24 14:51:24 +03:00
|
|
|
msg,
|
2021-02-19 16:32:02 +03:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!client.isConnected) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2021-02-24 14:51:24 +03:00
|
|
|
let result = await sendParticleAsFetch<string[][]>(client, particle, callbackFn, callbackService);
|
2021-02-19 16:32:02 +03:00
|
|
|
if (result[0][0] != msg) {
|
2021-02-24 14:51:24 +03:00
|
|
|
log.warn("unexpected behavior. 'identity' must return arguments the passed arguments.");
|
2021-02-19 16:32:02 +03:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
2021-02-24 14:51:24 +03:00
|
|
|
log.error('Error on establishing connection: ', e);
|
2021-02-19 16:32:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
2021-02-24 14:51:24 +03:00
|
|
|
};
|
2021-02-25 18:36:10 +03:00
|
|
|
|
|
|
|
export const subscribeForErrors = (client: FluenceClient, ttl: number): Promise<void> => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
registerServiceFunction(client, '__magic', 'handle_xor', (args, _) => {
|
|
|
|
setTimeout(() => {
|
|
|
|
try {
|
|
|
|
reject(JSON.parse(args[0]));
|
|
|
|
} catch {
|
|
|
|
reject(args);
|
|
|
|
}
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
unregisterServiceFunction(client, '__magic', 'handle_xor');
|
|
|
|
return {};
|
|
|
|
});
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
unregisterServiceFunction(client, '__magic', 'handle_xor');
|
|
|
|
resolve();
|
|
|
|
}, ttl);
|
|
|
|
});
|
|
|
|
};
|