fluence-js/src/internal/FluenceConnection.ts

182 lines
5.7 KiB
TypeScript
Raw Normal View History

2020-05-14 15:20:39 +03:00
/*
2020-05-14 17:30:17 +03:00
* Copyright 2020 Fluence Labs Limited
2020-05-14 15:20:39 +03:00
*
2020-05-14 17:30:17 +03:00
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
2020-05-14 15:20:39 +03:00
*
2020-05-14 17:30:17 +03:00
* http://www.apache.org/licenses/LICENSE-2.0
2020-05-14 15:20:39 +03:00
*
2020-05-14 17:30:17 +03:00
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
2020-05-14 15:20:39 +03:00
*/
2020-12-23 17:24:22 +03:00
import Websockets from 'libp2p-websockets';
import Mplex from 'libp2p-mplex';
import Peer from 'libp2p';
import { decode, encode } from 'it-length-prefixed';
import pipe from 'it-pipe';
2020-09-15 12:09:13 +03:00
import * as log from 'loglevel';
import { logParticle, parseParticle, Particle, toPayload } from './particle';
import { NOISE } from '@chainsafe/libp2p-noise';
import PeerId from 'peer-id';
import { Multiaddr } from 'multiaddr';
2021-04-13 15:11:52 +03:00
import { all as allow_all } from 'libp2p-websockets/src/filters';
2020-05-14 15:20:39 +03:00
2021-08-24 17:37:03 +03:00
export const PROTOCOL_NAME = '/fluence/particle/2.0.0';
2020-05-14 15:20:39 +03:00
enum Status {
2020-12-23 17:24:22 +03:00
Initializing = 'Initializing',
Connected = 'Connected',
Disconnected = 'Disconnected',
2020-05-14 15:20:39 +03:00
}
/**
* Options to configure fluence connection
*/
export interface FluenceConnectionOptions {
/**
* @property {number} [checkConnectionTTL] - TTL for the check connection request in ms
*/
checkConnectionTTL?: number;
/**
* @property {number} [checkConnectionTTL] - set to true to skip check connection request completely
*/
skipCheckConnection?: boolean;
/**
* @property {number} [dialTimeout] - How long a dial attempt is allowed to take.
*/
dialTimeout?: number;
}
2020-05-14 15:20:39 +03:00
export class FluenceConnection {
private readonly selfPeerId: PeerId;
private node: Peer;
2020-05-14 15:20:39 +03:00
private readonly address: Multiaddr;
2020-07-27 16:39:54 +03:00
readonly nodePeerId: PeerId;
private readonly selfPeerIdStr: string;
private readonly handleParticle: (call: Particle) => void;
constructor(
multiaddr: Multiaddr,
hostPeerId: PeerId,
selfPeerId: PeerId,
handleParticle: (call: Particle) => void,
) {
this.selfPeerId = selfPeerId;
2020-12-24 19:47:17 +03:00
this.handleParticle = handleParticle;
this.selfPeerIdStr = selfPeerId.toB58String();
2020-05-14 15:20:39 +03:00
this.address = multiaddr;
this.nodePeerId = hostPeerId;
2020-06-30 16:34:05 +03:00
}
async connect(options?: FluenceConnectionOptions) {
2021-04-13 15:11:52 +03:00
await this.createPeer(options);
await this.startReceiving();
}
isConnected() {
return this.status === Status.Connected;
}
// connection status. If `Disconnected`, it cannot be reconnected
private status: Status = Status.Initializing;
private async createPeer(options?: FluenceConnectionOptions) {
const peerInfo = this.selfPeerId;
const transportKey = Websockets.prototype[Symbol.toStringTag];
2020-05-14 15:20:39 +03:00
this.node = await Peer.create({
peerId: peerInfo,
2020-05-14 15:20:39 +03:00
modules: {
transport: [Websockets],
streamMuxer: [Mplex],
connEncryption: [NOISE],
2020-05-14 15:20:39 +03:00
},
2021-04-13 15:11:52 +03:00
config: {
transport: {
[transportKey]: {
filter: allow_all,
},
},
2021-04-13 15:11:52 +03:00
},
dialer: {
dialTimeout: options?.dialTimeout,
},
2020-05-14 15:20:39 +03:00
});
}
private async startReceiving() {
if (this.status === Status.Initializing) {
await this.node.start();
log.debug(`dialing to the node with client's address: ` + this.node.peerId.toB58String());
2020-05-14 15:20:39 +03:00
try {
await this.node.dial(this.address);
} catch (e) {
if (e.name === 'AggregateError' && e._errors.length === 1) {
const error = e._errors[0];
throw `Error dialing node ${this.address}:\n${error.code}\n${error.message}`;
} else {
throw e;
}
}
2020-05-14 15:20:39 +03:00
2020-12-23 17:24:22 +03:00
this.node.handle([PROTOCOL_NAME], async ({ connection, stream }) => {
pipe(stream.source, decode(), async (source: AsyncIterable<string>) => {
2020-12-23 17:24:22 +03:00
for await (const msg of source) {
try {
const particle = parseParticle(msg);
logParticle(log.debug, 'Particle is received:', particle);
this.handleParticle(particle);
2020-12-23 17:24:22 +03:00
} catch (e) {
log.error('error on handling a new incoming message: ' + e);
2020-05-14 15:20:39 +03:00
}
}
2020-12-23 17:24:22 +03:00
});
2020-05-14 15:20:39 +03:00
});
this.status = Status.Connected;
} else {
throw Error(`can't start receiving. Status: ${this.status}`);
}
}
private checkConnectedOrThrow() {
if (this.status !== Status.Connected) {
2020-12-23 17:24:22 +03:00
throw Error(`connection is in ${this.status} state`);
2020-05-14 15:20:39 +03:00
}
}
async disconnect() {
await this.node.stop();
this.status = Status.Disconnected;
}
async sendParticle(particle: Particle): Promise<void> {
2020-09-28 17:01:49 +03:00
this.checkConnectedOrThrow();
let action = toPayload(particle);
2020-12-24 19:11:10 +03:00
let particleStr = JSON.stringify(action);
logParticle(log.debug, 'send particle: \n', particle);
2020-05-14 15:20:39 +03:00
// create outgoing substream
2020-12-23 17:24:22 +03:00
const conn = (await this.node.dialProtocol(this.address, PROTOCOL_NAME)) as {
stream;
2020-12-23 17:24:22 +03:00
protocol: string;
};
2020-05-14 15:20:39 +03:00
pipe(
2020-11-11 22:05:54 +03:00
[Buffer.from(particleStr, 'utf8')],
2020-05-14 15:20:39 +03:00
// at first, make a message varint
encode(),
conn.stream.sink,
);
}
2020-06-19 14:29:06 +03:00
}