Commit d63007f4 authored by lazorfuzz's avatar lazorfuzz
Browse files

Self optimizing partial mesh networks

parent 6b9794e0
{
"presets": ["env"]
}
*/node_modules
*.log
......@@ -6,6 +6,10 @@
"codeFrame": false
},
"extends": "airbnb",
"plugins": ["jest"],
"env": {
"jest/globals": true
},
"rules": {
"max-len": 0,
"no-multi-assign": 0,
......@@ -13,6 +17,14 @@
"no-param-reassign": 0,
"no-shadow": 0,
"consistent-return": 0,
"guard-for-in": 0
"guard-for-in": 0,
"react/jsx-filename-extension": 0,
"jsx-a11y/media-has-caption": 0,
"react/prop-types": 0,
"react/prefer-stateless-function": 0,
"no-underscore-dangle": 0,
"comma-dangle": 0,
"class-methods-use-this": 0,
"no-loop-func": 0
}
}
FROM node:8.11.4
WORKDIR /app/website
EXPOSE 3000 35729
COPY ./docs /app/docs
COPY ./website /app/website
RUN yarn install
CMD ["yarn", "start"]
# LioWebRTC
An Electron-compatible WebRTC library that makes it easy to embed scalable peer to peer communication into React components.
A WebRTC library that makes it easy to embed scalable peer to peer communication into UI components.
LioWebRTC was built on SimpleWebRTC, and modified to be compatible with React, JSX, and Electron. It can also be configured for scalability using partial mesh networks, making it possible to emit data via data channels to thousands of peers in a room, while only needing to be connected to at least one other peer in the room.
LioWebRTC works standalone, but it is also compatible with React, Vue, Electron, etc. It can be configured for scalability using partial mesh networks, making it possible to emit data to thousands of peers in a room, while only needing to be connected to at least one other peer in the room.
[Click here](https://chatdemo.razorfart.com/) to see a chatroom demo built with React and LioWebRTC.
Peers in a LioWebRTC partial mesh network can self-optimize by default; each peer caches portions of the entire p2p network, and sends their cached graphs to newly joined peers. That means a peer can build an almost complete view of the entire graph without having to query each node (+1 scalability 😉).
[Click here](https://vchatdemo.razorfart.com/) to see a video conferencing demo app built with React and LioWebRTC.
[Click here](https://chatdemo.razorfart.com/) to see a chatroom demo built with LioWebRTC.
[Click here](https://vchatdemo.razorfart.com/) to see a video conferencing demo app built with LioWebRTC.
## Using LioWebRTC with React
React developers may want to take a look at [react-liowebrtc](https://github.com/lazorfuzz/react-liowebrtc).
## Usage
......@@ -26,14 +31,10 @@ import LioWebRTC from 'liowebrtc';
By default, this enables video, audio, and data channels.
```js
const webrtc = new LioWebRTC({
// The local video ref set within your render function, or the element's id
localVideoEl: localVideoIdOrRef,
// Immediately request camera and mic access
autoRequestMedia: true,
// Displays events emitted by the webrtc object in the console
debug: true,
// The url for your signaling server
url: 'https://sandbox.simplewebrtc.com:443/'
localVideoEl: localVideoIdOrRef, // The local video element
autoRequestMedia: true, // Immediately request camera and mic access upon initialization
debug: true, // Displays events emitted by liowebrtc in the console
url: 'https://your-signaling-server.com:443/' // The url for your signaling server. If no url is passed, liowebrtc uses the default demo signaling server. (The default server is for demo purposes only, and is not reliable. Plus, I'm the only one paying for it 🙁. Please use your own in production!)
});
```
......@@ -57,6 +58,18 @@ const webrtc = new LioWebRTC({
});
```
### Partial mesh network
Peers only form direct connections with a maximum of maxPeers and a minimum of minPeers. shout()ing still works because peers wil re-propagate messages to other peers.
```js
const webrtc = new LioWebRTC({
dataOnly: true,
network: {
maxPeers: 8,
minPeers: 4
}
})
```
### Join a room once it's ready
```js
......@@ -70,13 +83,13 @@ webrtc.on('ready', () => {
Sometimes a peer wants to let every other peer in the room to know about something. This can be accomplished with
```shout(messageType, payload)```
```js
webrtc.shout('taskCompleted', { success: true, id: '137' });
webrtc.shout('event-label', { success: true, payload: '137' });
```
Now for the recipients, handle the peer event with a listener:
```js
webrtc.on('receivedPeerData', (type, data, peer) => {
if (type === 'taskCompleted' && data.success) {
console.log(`Peer ${peer.id} completed task ${data.id}`);
if (type === 'event-label' && data.success) {
console.log(`Peer ${peer.id} emitted ${data.payload}`);
}
});
```
......@@ -101,9 +114,6 @@ componentDidUpdate(prevProps, prevState) {
this.webrtc.shout('stateUpdate', this.state);
}
}
this.webrtc.on('receivedPeerData', (type, state, peer) => {
if (type === 'stateUpdate') this.setState({ peerState: state });
});
```
......@@ -111,7 +121,7 @@ All communications via shout/whisper are sent over the default data channel and
### Attaching a peer's media stream to a video element
```js
webrtc.on('videoAdded', (stream, peer) => {
webrtc.on('peerStreamAdded', (stream, peer) => {
webrtc.attachStream(stream, yourVideoElementOrRef);
});
```
......@@ -141,7 +151,7 @@ class Party extends Component {
componentDidMount() {
this.webrtc = new LioWebRTC({
// The url for your signaling server. Use your own in production!
url: 'https://sandbox.simplewebrtc.com:443/',
url: 'https://sm1.lio.app:443/',
// The local video ref set within your render function
localVideoEl: this.localVid,
// Immediately request camera access
......@@ -151,8 +161,8 @@ class Party extends Component {
debug: true
});
this.webrtc.on('videoAdded', this.addVideo);
this.webrtc.on('videoRemoved', this.removeVideo);
this.webrtc.on('peerStreamAdded', this.addVideo);
this.webrtc.on('peerStreamRemoved', this.removeVideo);
this.webrtc.on('ready', this.readyToJoin);
this.webrtc.on('iceFailed', this.handleConnectionError);
this.webrtc.on('connectivityError', this.handleConnectionError);
......@@ -226,7 +236,7 @@ export default Party;
## API
### Constructor
### Constructor Options
`new LioWebRTC(options)`
......@@ -262,6 +272,15 @@ export default Party;
muted: true // mute local video stream to prevent echo
}
```
- `object network` - *optional* options for setting minimum and maximum peers to connect to.
Defaults to
```javascript
{
minPeers: 2, // connect to at least 2 peers
maxPeers: 0 // when 0, maxPeers is infinite
}
```
- `bool selfOptimize` - *optional(=true)* whether or not peers in a partial mesh network should self-optimize their connections. LioWebRTC uses a more object-oriented version of an adjacency list to represent the p2p graph, with the weights of the edges representing roundtrip latency between two nodes. With `selfOptimize` set to true, peers automatically disconnect from neighbors with latencies >=1 std. deviation from the mean.
### Fields
......@@ -276,7 +295,7 @@ constructor. Example:
```js
// Emitted when a peer's media stream becomes available
this.webrtc.on('videoAdded', (stream, peer) => {
this.webrtc.on('peerStreamAdded', (stream, peer) => {
// Attach the MediaStream to a video element
// this.webrtc.attachStream(stream, this.remoteVideos[peer.id]);
});
......@@ -305,12 +324,12 @@ ending all peers, and stopping local stream
`'ready', sessionId` - emitted when liowebrtc is ready to join a room
- `sessionId` - the socket.io connection session ID
`'receivedPeerData', type, payload, peer` - emitted when a peer sends data via `shout` or `whisper`
`'receivedPeerData', type, payload, peer` - emitted when data is received from a peer that sent the data with `shout` or `whisper`
- `type` a label, usually a string, that describes the payload
- `payload` any kind of data sent by the peer, usually an object
- `peer` the object representing the peer and its peer connection
`'receivedSignalData', type, payload, peer` - emitted when a peer sends data via `broadcast` or `transmit`
`'receivedSignalData', type, payload, peer` - emitted when data is received from a peer that sent the data via the socket.io signaling server with `broadcast` or `transmit`
- `type` a label, usually a string, that describes the payload
- `payload` any kind of data sent by the peer, usually an object
- `peer` the object representing the peer and its peer connection
......@@ -322,11 +341,11 @@ ending all peers, and stopping local stream
`'unmute', data` - emitted when a peer mutes their video or audioOn
- `data` an object that contains an `id` property for the id of the peer that sent the event, and a `name` property that indicates which stream was muted, `video` or `audio`
`'videoAdded', stream, peer` - emitted when a peer's MediaStream becomes available
`'peerStreamAdded', stream, peer` - emitted when a peer's MediaStream becomes available
- `stream` - the MediaStream associated with the peer
- `peer` - the peer associated with the stream that was added
`'videoRemoved', peer` - emitted when a peer stream is removed
`'peerStreamRemoved', peer` - emitted when a peer stream is removed
- `peer` - the peer associated with the stream that was removed
### Methods
......@@ -351,9 +370,14 @@ room via the signaling server (similar to `shout`, but not p2p). Listen for peer
`emit(eventLabel, ...args)` - emit arbitrary event (Emits locally. To emit stuff other peers, use `shout`)
`getClients((err, clients))` - asks the socket.io signaling server for a list of peers currently in the room.
- `object clients` - An object whose keys are the client IDs and values are client types.
`getContainerId(peer)` - get the DOM id associated with a peer's media element. In JSX, you will need to set the id of the container element to this value
- `Peer peer` - the object representing the peer and its peer connection
`getMyId()` - get your own peer ID
`getId(peer)` - get the DOM id associated with a peer's media stream. In JSX, you will need to set the id of the peer's media element to this value.
- `Peer peer` - the object representing the peer and its peer connection
......@@ -368,7 +392,7 @@ room via the signaling server (similar to `shout`, but not p2p). Listen for peer
`joinRoom(name, callback)` - joins the room `name`. Callback is
invoked with `callback(err, roomDescription)` where `roomDescription` is yielded
by the connection on the `join` event. See [signalmaster](https://github.com/andyet/signalmaster) for details about rooms.
by the connection on the `join` event. See [SignalBuddy](https://github.com/lazorfuzz/signalbuddy) for more info.
`leaveRoom()` - leaves the currently joined room and stops local streams
......@@ -387,7 +411,7 @@ by the connection on the `join` event. See [signalmaster](https://github.com/and
`resumeVideo()` - resumes the video stream to your peers (resumes sending video in the WebRTC video channel)
`sendDirectlyToAll(messageType, payload, channel)` - sends a message
to all peers in the room via a data channel (same as `shout`, except you can specify your own data channel. Use this if you need to set up a new data channel, like a dedicated file-sharing channel, etc.)
to all peers in the room via a data channel (same as `shout`, except you can specify your own data channel. Use this if you need to set up a new data channel, e.g. a dedicated file-sharing channel, etc.)
- `string channel` - (optional) the name of the data channel. If it doesn't exist, it will be created.
`setVolumeForAll(volume)` - set the volume level for all peers
......@@ -421,14 +445,10 @@ room via the signaling server (similar to `whisper`, but not p2p). Listen for pe
WebRTC needs to be facilitated with signaling; a service that acts as a matchmaker for peers before they establish direct video/audio/data channels. Signaling can be done in any way, e.g. via good old fashioned carrier pigeons. Signaling services only need to fulfill the absolute minimal role of matchmaking peers.
[Signalmaster](https://github.com/andyet/signalmaster) is a [socket.io](http://socket.io/) server signaling solution, and is very easy to set up. socket.io enables real-time, bidirectional communication between a client and server via web sockets. It also allows us to easily segment peers into rooms.
[SignalBuddy](https://github.com/lazorfuzz/signalbuddy) is a scalable [socket.io](http://socket.io/) signaling server, and is very easy to set up. socket.io enables real-time, bidirectional communication between a client and server via web sockets. It also allows us to easily segment peers into rooms.
For emitting data to peers, LioWebRTC provides a unified, event-based API that enables peers to seamlessly switch between `shout`ing (p2p data channels) or `broadcast`ing (socket.io) to all the peers in a room. It's up to you to decide which protocol to use, but socket.io should ideally only be used for transmitting things like metadata, one-off events, etc. Both protocols are real-time, bidirectional, and event-based.
When joining a room, the client becomes a node in a full mesh network consisting of every other peer in the room, opening a unique data channel to each peer. For video conferencing, that means a unique video, audio, and data channel is opened for each peer.
The socket.io connection remains a single bi-directional pipeline between the client and the signaling server. It will always be a single connection no matter how many peers there are in the room.
### Connection
LioWebRTC wraps socketio-client and returns a connection object. This the connection to the signaling server. The connection object comes with the following methods:
......@@ -440,11 +460,4 @@ LioWebRTC wraps socketio-client and returns a connection object. This the connec
### Signaling Server Url
LioWebRTC uses the signalmaster server provided for testing purposes by SimpleWebRTC.
In production, you will need to set up your own [signalmaster](https://github.com/andyet/signalmaster) server (or any other socket.io solution that implements matchmaking).
To start your signalmaster server in production mode using PM2, do the following:
```
NODE_ENV=production pm2 start signalmaster
```
Then pass your server's url into your LioWebRTC instantiation config.
LioWebRTC uses SignalBuddy to facilitate signaling. LioWebRTC works out of the box with a demo SignalBuddy server that was intended for testing purposes. However, for production purposes, IT IS NOT RELIABLE. In production, you will need to set up your own [SignalBuddy](https://github.com/lazorfuzz/signalbuddy) server (or any other socket.io solution that implements matchmaking).
version: "3"
services:
docusaurus:
build: .
ports:
- 3000:3000
- 35729:35729
volumes:
- ./docs:/app/docs
- ./website/blog:/app/website/blog
- ./website/core:/app/website/core
- ./website/i18n:/app/website/i18n
- ./website/pages:/app/website/pages
- ./website/static:/app/website/static
- ./website/sidebars.json:/app/website/sidebars.json
- ./website/siteConfig.js:/app/website/siteConfig.js
working_dir: /app/website
---
id: doc1
title: Latin-ish
sidebar_label: Example Page
---
Check the [documentation](https://docusaurus.io) for how to use Docusaurus.
## Lorem
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
## Mauris In Code
```
Mauris vestibulum ullamcorper nibh, ut semper purus pulvinar ut. Donec volutpat orci sit amet mauris malesuada, non pulvinar augue aliquam. Vestibulum ultricies at urna ut suscipit. Morbi iaculis, erat at imperdiet semper, ipsum nulla sodales erat, eget tincidunt justo dui quis justo. Pellentesque dictum bibendum diam at aliquet. Sed pulvinar, dolor quis finibus ornare, eros odio facilisis erat, eu rhoncus nunc dui sed ex. Nunc gravida dui massa, sed ornare arcu tincidunt sit amet. Maecenas efficitur sapien neque, a laoreet libero feugiat ut.
```
## Nulla
Nulla facilisi. Maecenas sodales nec purus eget posuere. Sed sapien quam, pretium a risus in, porttitor dapibus erat. Sed sit amet fringilla ipsum, eget iaculis augue. Integer sollicitudin tortor quis ultricies aliquam. Suspendisse fringilla nunc in tellus cursus, at placerat tellus scelerisque. Sed tempus elit a sollicitudin rhoncus. Nulla facilisi. Morbi nec dolor dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras et aliquet lectus. Pellentesque sit amet eros nisi. Quisque ac sapien in sapien congue accumsan. Nullam in posuere ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin lacinia leo a nibh fringilla pharetra.
## Orci
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin venenatis lectus dui, vel ultrices ante bibendum hendrerit. Aenean egestas feugiat dui id hendrerit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur in tellus laoreet, eleifend nunc id, viverra leo. Proin vulputate non dolor vel vulputate. Curabitur pretium lobortis felis, sit amet finibus lorem suscipit ut. Sed non mollis risus. Duis sagittis, mi in euismod tincidunt, nunc mauris vestibulum urna, at euismod est elit quis erat. Phasellus accumsan vitae neque eu placerat. In elementum arcu nec tellus imperdiet, eget maximus nulla sodales. Curabitur eu sapien eget nisl sodales fermentum.
## Phasellus
Phasellus pulvinar ex id commodo imperdiet. Praesent odio nibh, sollicitudin sit amet faucibus id, placerat at metus. Donec vitae eros vitae tortor hendrerit finibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque vitae purus dolor. Duis suscipit ac nulla et finibus. Phasellus ac sem sed dui dictum gravida. Phasellus eleifend vestibulum facilisis. Integer pharetra nec enim vitae mattis. Duis auctor, lectus quis condimentum bibendum, nunc dolor aliquam massa, id bibendum orci velit quis magna. Ut volutpat nulla nunc, sed interdum magna condimentum non. Sed urna metus, scelerisque vitae consectetur a, feugiat quis magna. Donec dignissim ornare nisl, eget tempor risus malesuada quis.
---
id: doc2
title: document number 2
---
This is a link to [another document.](doc3.md)
This is a link to an [external page.](http://www.example.com)
---
id: doc3
title: This is document number 3
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac euismod odio, eu consequat dui. Nullam molestie consectetur risus id imperdiet. Proin sodales ornare turpis, non mollis massa ultricies id. Nam at nibh scelerisque, feugiat ante non, dapibus tortor. Vivamus volutpat diam quis tellus elementum bibendum. Praesent semper gravida velit quis aliquam. Etiam in cursus neque. Nam lectus ligula, malesuada et mauris a, bibendum faucibus mi. Phasellus ut interdum felis. Phasellus in odio pulvinar, porttitor urna eget, fringilla lectus. Aliquam sollicitudin est eros. Mauris consectetur quam vitae mauris interdum hendrerit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Duis et egestas libero, imperdiet faucibus ipsum. Sed posuere eget urna vel feugiat. Vivamus a arcu sagittis, fermentum urna dapibus, congue lectus. Fusce vulputate porttitor nisl, ac cursus elit volutpat vitae. Nullam vitae ipsum egestas, convallis quam non, porta nibh. Morbi gravida erat nec neque bibendum, eu pellentesque velit posuere. Fusce aliquam erat eu massa eleifend tristique.
Sed consequat sollicitudin ipsum eget tempus. Integer a aliquet velit. In justo nibh, pellentesque non suscipit eget, gravida vel lacus. Donec odio ante, malesuada in massa quis, pharetra tristique ligula. Donec eros est, tristique eget finibus quis, semper non nisl. Vivamus et elit nec enim ornare placerat. Sed posuere odio a elit cursus sagittis.
Phasellus feugiat purus eu tortor ultrices finibus. Ut libero nibh, lobortis et libero nec, dapibus posuere eros. Sed sagittis euismod justo at consectetur. Nulla finibus libero placerat, cursus sapien at, eleifend ligula. Vivamus elit nisl, hendrerit ac nibh eu, ultrices tempus dui. Nam tellus neque, commodo non rhoncus eu, gravida in risus. Nullam id iaculis tortor.
Nullam at odio in sem varius tempor sit amet vel lorem. Etiam eu hendrerit nisl. Fusce nibh mauris, vulputate sit amet ex vitae, congue rhoncus nisl. Sed eget tellus purus. Nullam tempus commodo erat ut tristique. Cras accumsan massa sit amet justo consequat eleifend. Integer scelerisque vitae tellus id consectetur.
---
id: doc4
title: Other Document
---
this is another document
---
id: doc5
title: Fifth Document
---
Another one
module.exports = {
// The bail config option can be used here to have Jest stop running tests after
// the first failure.
bail: false,
// Indicates whether each individual test should be reported during the run.
verbose: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: false,
// The directory where Jest should output its coverage files.
coverageDirectory: './coverage/',
// If the test path matches any of the patterns, it will be skipped.
testPathIgnorePatterns: ['<rootDir>/node_modules/'],
// If the file path matches any of the patterns, coverage information will be skipped.
coveragePathIgnorePatterns: ['<rootDir>/node_modules/'],
// The pattern Jest uses to detect test files.
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$',
// This option sets the URL for the jsdom environment.
// It is reflected in properties such as location.href.
// @see: https://github.com/facebook/jest/issues/6769
testURL: 'http://localhost/',
};
......@@ -3,9 +3,10 @@
"version": "0.1.14",
"repository": "https://github.com/lazorfuzz/liowebrtc",
"main": "./dist/liowebrtc.js",
"description": "An Electron-compatible WebRTC library that makes it easy to embed peer to peer communication into React components.",
"description": "A WebRTC library that makes it easy to embed peer to peer communication into UI components.",
"dependencies": {
"attachmediastream": "^2.0.0",
"eslint-plugin-jest": "^21.24.1",
"filetransfer": "^2.0.4",
"getscreenmedia": "^5.0.0",
"hark": "^1.2.0",
......@@ -16,16 +17,19 @@
"wildemitter": "^1.2.0"
},
"devDependencies": {
"@types/jest": "^23.3.5",
"babel-cli": "^6.26.0",
"babel-eslint": "^8.2.3",
"babel-jest": "^23.6.0",
"babel-preset-env": "^1.7.0",
"chromedriver": "^2.29.0",
"eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-react": "^7.4.0",
"eslint-plugin-react": "^7.11.1",
"geckodriver": "^1.6.1",
"jest": "^23.6.0",
"request": "^2.72.0",
"selenium-webdriver": "^3.0.1",
"tape": "^4.0.0",
......@@ -36,6 +40,7 @@
"scripts": {
"build": "babel src --presets babel-preset-env --out-dir dist",
"test-travis": "test/run-selenium",
"test-graph": "jest",
"lint": "eslint --cache --fix .",
"validate": "npm ls",
"test-page": "echo \"open https://localhost:8443/test/\" && stupid-server -s",
......
export default class Edge {
constructor(startNode, endNode, weight = 0) {
this.node1 = startNode;
this.node2 = endNode;
this.weight = weight;
}
getId() {
const startNodeId = this.node1.getId();
const endNodeId = this.node2.getId();
return `${startNodeId}_${endNodeId}`;
}
getWeight() {
return this.weight;
}
setWeight(weight) {
this.weight = weight;
}
reverse() {
const tmp = this.node1;
this.node1 = this.node2;
this.node2 = tmp;
}
toString() {
return this.getId();
}
}
export default class PeerGraph {
constructor() {
this.nodes = {};
this.edges = {};
this.edgeCount = 0;
}
addEdge(edge) {
let node1 = this.getNodeById(edge.node1.getId());
let node2 = this.getNodeById(edge.node2.getId());
if (!node1) {
this.addNode(edge.node1);
node1 = this.getNodeById(edge.node1.getId());
}
if (!node2) {
this.addNode(edge.node2);
node2 = this.getNodeById(edge.node2.getId());
}
if (this.edges[edge.getId()]) {
// throw new Error('Edge already exists');
} else {
this.edges[edge.getId()] = edge;
}
// Add edge to both node instances because it's an undirected graph
node1.addEdge(edge);
node2.addEdge(edge);
return this;
}
addNode(newNode) {
this.nodes[newNode.getId()] = newNode;
return this;
}
getNodeById(id) {
return this.nodes[id];
}
getNeighbors(node) {
return node.getNeighbors();
}
getWeight() {
return this.getAllEdges().reduce((weight, edge) => weight + edge.weight, 0);
}
getAllNodes() {
return Object.values(this.nodes);
}
getAllEdges() {
return Object.values(this.edges);
}
findNodeById(nodeId) {
if (this.nodes[nodeId]) {
return this.nodes[nodeId];
}
return null;
}
findEdge(node1, node2) {
const node = this.getNodeById(node1.getId());
if (!node) {
return null;
}
return node.findEdge(node2);
}
deleteEdge(edge) {
if (this.edges[edge.getId()]) {
delete this.edges[edge.getId()];
}
const node1 = this.getNodeById(edge.node1.getId());
const node2 = this.getNodeById(edge.node2.getId());
node1.deleteEdge(edge);
node2.deleteEdge(edge);
}
getNodeIndices() {
const nodeIndices = {};
this.getAllNodes().forEach((node, index) => {
nodeIndices[node.getId()] = index;
});
return nodeIndices;
}
toString() {
return Object.keys(this.nodes).toString();
}
toJSON() {
}
}
export default class Node {
constructor(value) {
if (value === undefined) {
throw new Error('Node must have an ID');
}
this.value = value;
this.edges = {};
}
addEdge(edge) {
this.edges[edge.getId()] = edge;
return this;
}
deleteEdge(edge) {
delete this.edges[edge.getId()];
}
getEdges() {
return Object.values(this.edges);
}
getDegree() {
return Object.keys(this.edges).length;
}
getNeighbors() {
const edges = Object.values(this.edges);
const nodes = edges.map(e => (e.node1 === this ? e.node2 : e.node1));
return nodes;
}
hasEdge(requiredEdge) {
const edgeNode = this.edges.filter(edge => edge.getId() === requiredEdge.getId());
return !!edgeNode.length;
}
hasNeighbor(node) {
const nodeWeWant = Object.values(this.edges).filter(e => e.node1.getId() === node.getId() || e.node2.getId() === node.getId());
return !!nodeWeWant.length;
}
findEdge(node) {
const result = Object.values(this.edges).filter(e => e.node1.getId() === node.getId() || e.node2.getId() === node.getId());
return result.length ? result[0] : null;
}
getId() {
return this.value;
}
deleteAllEdges() {
this.getEdges().forEach(e => this.deleteEdge(e));
return this;
}
toString() {
return `${this.value}`;
}