├── .idea
├── .name
├── .gitignore
├── misc.xml
├── vcs.xml
├── jsLibraryMappings.xml
├── modules.xml
└── b2bjs.iml
├── signalling_server.js
├── stats
├── FileStatsRecorder.js
├── StatsRecorder.js
├── HTTPStatsRecorder.js
├── ProximityLinkChangeProbe.js
├── Stats Objects Standard.txt
├── NodeStatsProbe.js
├── StatsObjURLTagger.js
└── EncounterIntervalProbe.js
├── controllers
├── NodeController.js
├── SearchRequest.js
├── ProximityLinkBooster.js
├── SearchRelay.js
└── SearchResponder.js
├── config.js
├── services
└── ClientInfoService.js
├── test.js
├── constants.js
├── index.html
├── package.json
├── index.js
├── test
└── ListManagerTest.js
├── proximity
├── ProximityList.js
└── ListManager.js
├── README.md
├── thirdparty
└── adapter.js
└── node.js
/.idea/.name:
--------------------------------------------------------------------------------
1 | b2bjs
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
--------------------------------------------------------------------------------
/signalling_server.js:
--------------------------------------------------------------------------------
1 | process.env.PORT = process.argv[2];
2 | let server = require("cyclon.p2p-rtc-server/server");
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/stats/FileStatsRecorder.js:
--------------------------------------------------------------------------------
1 | let StatsRecorder = require('./StatsRecorder');
2 |
3 | class FileStatsRecorder extends StatsRecorder{
4 | __handleStats(statsObj) {
5 | super.__handleStats();
6 |
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/stats/StatsRecorder.js:
--------------------------------------------------------------------------------
1 | const constants = require('../constants');
2 | class StatsRecorder{
3 | constructor(statsRecorder){
4 | this.eventEmitters = [];
5 | this.eventsBacklog = [];
6 | }
7 | addEventEmitter(ee){
8 | this.eventEmitters.push(ee) ;
9 | ee.on("stats",(statsObj)=>{
10 | this.__handleStats(statsObj);
11 | });
12 | }
13 |
14 | __handleStats(statsObj){
15 | //override in subclass
16 | }
17 | }
18 |
19 | module.exports = StatsRecorder;
20 |
21 |
--------------------------------------------------------------------------------
/.idea/b2bjs.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/controllers/NodeController.js:
--------------------------------------------------------------------------------
1 | var EventEmitter = require("events").EventEmitter;
2 |
3 | class NodeController extends EventEmitter{
4 | constructor(node) {
5 | super();
6 | this.node = node;
7 | }
8 |
9 | /**
10 | *
11 | * @param packet
12 | * @return {Boolean} true if packet could be handled, false if this controller can't handle the given packet
13 | */
14 | handlePacket(packet){
15 | // override in subclass
16 | throw new Error("should handle in subclass");
17 | }
18 |
19 | /**
20 | *
21 | * @param packet
22 | *
23 | */
24 | sendOutPacket(packet,targetNode){
25 | packet["date"] = new Date().getDate();
26 | this.node.sendObjectToNode(packet, targetNode);
27 | }
28 |
29 |
30 | }
31 |
32 | module.exports = NodeController;
33 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | let CONFIG = {
2 | NEIGHBOR_SIZE: 7,
3 | SHUFFLE_SIZE: 3,
4 | TICK_INTERVAL: 20000,
5 | DEFAULT_SIGNALLING_SERVERS: [
6 | {
7 | "socket": {
8 | "server": "http://localhost:12345"
9 | },
10 | "signallingApiBase": "http://localhost:12345"
11 | },
12 | {
13 | "socket": {
14 | "server": "http://localhost:12346"
15 | },
16 | "signallingApiBase": "http://localhost:12346"
17 | }
18 | ],
19 | DEFAULT_BATCHING_DELAY_MS: 300,
20 | DEFAULT_ICE_SERVERS: [
21 | // The public Google STUN server
22 | {urls: ['stun:stun.l.google.com:19302']},
23 | ],
24 | DEFAULT_CHANNEL_STATE_TIMEOUT_MS: 30000, DEFAULT_SIGNALLING_SERVER_RECONNECT_DELAY_MS: 5000
25 | };
26 |
27 | module.exports = CONFIG;
28 |
--------------------------------------------------------------------------------
/services/ClientInfoService.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function ClientInfoService(StorageService) {
4 |
5 | var infoKey = "cyclonDemoClientInfo";
6 | var neighbourCacheKey = "cyclonDemoNeighbourCacheKey";
7 |
8 | return {
9 |
10 | getClientInfo: function () {
11 | return StorageService.getItem(infoKey);
12 | },
13 |
14 | setClientInfo: function (value) {
15 | StorageService.setItem(infoKey, value);
16 | },
17 |
18 | getStoredNeighbourCache: function() {
19 | var storedValue = StorageService.getItem(neighbourCacheKey);
20 | return storedValue ? JSON.parse(storedValue) : null;
21 | },
22 |
23 | setStoredNeighbourCache: function(value) {
24 | StorageService.setItem(neighbourCacheKey, JSON.stringify(value));
25 | }
26 | };
27 | }
28 |
29 | module.exports = ClientInfoService;
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | function hello(callback) {
2 | callback();
3 | }
4 |
5 | class Test {
6 | constructor(){
7 | }
8 | test2(){
9 | const a = [];
10 | a.push("a1");
11 | console.log(a);
12 | }
13 |
14 | test(){
15 | let entries = [4];
16 | let a = [1, 2, 3];
17 | entries.push(...a);
18 | console.info(entries);
19 | }
20 |
21 | hello2(){
22 | console.log("hello2");
23 | }
24 | }
25 |
26 | // console.log(new Date().getTime()/1000);
27 |
28 | let a = new Date().getTime()/1000;
29 |
30 | console.log(1569569502.117 -1569569499.472 >1 );
31 | console.log(5.007999897003174 - 1> 1);
32 | setTimeout(()=>{
33 | console.log((new Date().getTime()/1000)-a);
34 | },5000);
35 |
36 | // new Test().test();
37 | // let a = [3,2,7,0];
38 | // a.splice(1, 0, 80);
39 | // console.log(a);
40 | // console.log(a.slice(0, a.length));
41 |
42 | let {x,y=10} = {x:3}
43 | console.log(y);
44 |
--------------------------------------------------------------------------------
/constants.js:
--------------------------------------------------------------------------------
1 | module.exports.PACKET_TYPE = {
2 | SEARCH_REQ:"search_req",
3 | SEARCH_RES:"search_res",
4 | PROXIMITY_LINKS: "proximity_links"
5 | };
6 |
7 | module.exports.PACKET_FIELD = {
8 | DATE_TIME: "date_time",
9 | PACKET_TYPE: "packet_type",
10 | PACKET_ID: "packet_id",
11 | REQUEST_ID: "request_id",
12 | PACKET_SOURCE: "packet_source",
13 | QUERY: "query",
14 | HOPS: "hops",
15 | BODY:"body",
16 | LIST: "list",
17 | POINTERS: "pointers",
18 | ENTRY: ""
19 | };
20 |
21 | module.exports.EVENTS = {
22 | SEARCH_START: 'start',
23 | SEARCH_RELAY: 'relay',
24 | SEARCH_DISCARDED: 'discard',
25 | SEARCH_REVISITED: 'revisit',
26 | SEARCH_RESPOND: 'respond',
27 | PROX_LINK_CHANGED: 'prox_change',
28 | NODE_STATS: 'node_stats',
29 | ENC_INTERVAL: 'enc_interval'
30 | };
31 |
32 | module.exports.STATS_RECORDER = {
33 | HTTP: "http",
34 | FILE: "file",
35 | };
36 |
37 | module.exports.STATS_FILE_PATH = './stats.txt';
38 | module.exports.STATS_HTTP_URL = "http://localhost:3500/stats";
39 |
--------------------------------------------------------------------------------
/stats/HTTPStatsRecorder.js:
--------------------------------------------------------------------------------
1 | let StatsRecorder = require('./StatsRecorder');
2 | let StatsTagger = require('./StatsObjURLTagger');
3 | let cyclonRtc = require('cyclon.p2p-rtc-client');
4 |
5 | class HTTPStatsRecorder extends StatsRecorder{
6 | constructor(){
7 | super();
8 | this.tagger = new StatsTagger();
9 | }
10 | __handleStats(statsObj) {
11 | this.tagger.tagStatsObj(statsObj);
12 | this.__sendStats(statsObj);
13 | }
14 |
15 | /**
16 | * tries sending the obj over the network if it fails adds it to backlog
17 | * @param statsObj
18 | * @private
19 | */
20 | __sendStats(statsObj){
21 | let httpReq = new cyclonRtc.HttpRequestService();
22 | httpReq.get(statsObj.url).then((resolve)=>{
23 | // sent successfully
24 | },(reject)=>{
25 | // couldnt send save for later
26 | console.error("couldnt send stats obj to url: "+statsObj.url);
27 | console.error(reject);
28 | this.eventsBacklog.push(statsObj);
29 | });
30 | }
31 | }
32 |
33 |
34 |
35 |
36 | module.exports = HTTPStatsRecorder;
37 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Cyclon node test
7 |
24 |
25 |
26 | name:
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "meshp2p",
3 | "version": "1.0.30",
4 | "main": "node.js",
5 | "scripts": {
6 | "test": "mocha 'test/*' || true",
7 | "build": "browserify index.js -o bundle.js",
8 | "server": "node signalling_server.js"
9 | },
10 | "keywords": [
11 | "browser",
12 | "network",
13 | "mesh",
14 | "search",
15 | "distributed",
16 | "distributed search",
17 | "discovery",
18 | "vicinity",
19 | "p2p",
20 | "webrtc",
21 | "cyclon",
22 | "browser p2p",
23 | "gossip",
24 | "rtc"
25 | ],
26 | "author": "Hadi Modarres (github.com/HadiModarres)",
27 | "license": "ISC",
28 | "description": "https://github.com/HadiModarres/MeshP2P",
29 | "dependencies": {
30 | "angular": "^1.7.9",
31 | "browserify": "^16.5.0",
32 | "chai": "^4.2.0",
33 | "cyclon.p2p": "^1.0.0",
34 | "cyclon.p2p-common": "^0.1.11",
35 | "cyclon.p2p-rtc-client": "^1.0.2",
36 | "cyclon.p2p-rtc-comms": "^1.1.1",
37 | "cyclon.p2p-rtc-server": "^1.0.2",
38 | "faker": "^4.1.0",
39 | "mocha": "^6.2.2",
40 | "string-similarity": "^3.0.0",
41 | "uuid": "^3.3.3",
42 | "watchify": "^3.11.1",
43 | "webrtc-adapter": "^7.3.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/stats/ProximityLinkChangeProbe.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require("events").EventEmitter;
2 | var cyclonRtc = require('cyclon.p2p-rtc-client');
3 | let constants = require("../constants");
4 | /**
5 | * Has the responsibility to gather and report number of shuffles between encounters of other nodes. This data
6 | * can possibly be used to measure network size.
7 | */
8 | class ProximityLinkChangeProbe extends EventEmitter{
9 | constructor(node){
10 | super();
11 | this.node = node;
12 | this._gatherData();
13 | }
14 |
15 | _gatherData(){
16 | this.node.on("neighbors_updated", (beforeKeys, afterKeys) => {
17 | let httpReq = new cyclonRtc.HttpRequestService();
18 | let changed = this.__getDifferenceCount(beforeKeys,afterKeys);
19 | httpReq.get(`http://localhost:3500/stats/link_changed?count=${changed}`);
20 | });
21 | }
22 |
23 | __getDifferenceCount(arr1,arr2){
24 | let min = Math.min(arr1.length, arr2.length);
25 | let diffCount = 0;
26 | for (let i =0;i EVENTS enum
4 |
5 | Search Request:
6 | {
7 | event
8 | id: search request id
9 | source_name: requesting node name
10 | query: search query
11 | }
12 |
13 |
14 | Search Relay:
15 | {
16 | event
17 | id: search request id
18 | source_name: requesting node name
19 | target_name: target node name
20 | }
21 |
22 |
23 | Search Response:
24 | {
25 | event
26 | id: search request id
27 | source_name: requesting node name
28 | }
29 |
30 | Search Discard:
31 | {
32 | event
33 | id: search request id
34 | source_name: requesting node name
35 | }
36 |
37 | Search Revisit:
38 | {
39 | event
40 | id: search request id
41 | source_name: requesting node name
42 | }
43 |
44 | Encounter Interval:
45 | {
46 | event
47 | sample_size
48 | interval_count
49 | total_avg
50 | source_name: requesting node name
51 | }
52 |
53 | Node Stats:
54 | {
55 | id: node name,
56 | neighbors: [node_name] : array of other neighbor names,
57 | shuffles: shuffle count
58 | timeouts: timeout count
59 | errors: error count
60 | enc_interval: encounter interval total average
61 | }
62 |
63 |
64 | Proximity List Changed:
65 | {
66 | event
67 | source_name
68 | }
69 |
--------------------------------------------------------------------------------
/stats/NodeStatsProbe.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require("events").EventEmitter;
2 | let IntervalProbe = require("./EncounterIntervalProbe");
3 |
4 | class NodeStatsProbe extends EventEmitter{
5 | constructor(node,reportInterval){
6 | super();
7 | this.node = node;
8 | this.intervalProbe = new IntervalProbe(this.node,1,4);
9 | this._initStats();
10 | this._setupListeners();
11 | this._startEmitting();
12 | this.reportInterval = reportInterval;
13 | }
14 |
15 | _startEmitting(){
16 | if (!this.node.listManager.getAllProximityLists("list#name")){
17 | setTimeout(()=>{this._startEmitting()},this.reportInterval);
18 | return;
19 | }
20 |
21 | let proxList = this.node.listManager.getAllProximityLists("list#name")[0];
22 | let neighbors = proxList.getAllElements();
23 | neighbors = neighbors.concat(this.node.listManager.getInboundListForGlobalList("list#name"));
24 | neighbors = neighbors.map((value) => {
25 | return value.key;
26 | });
27 | let statsObj = {event: "node_stats",id:this.node.name,neighbors,shuffles:this.shuffleCount,timeouts:this.timeoutCount,
28 | errors:this.errorCount,enc_interval: this.enc_interval};
29 | this.emit("stats", statsObj);
30 | setTimeout(()=>{this._startEmitting()},this.reportInterval);
31 | }
32 |
33 | _setupListeners(){
34 | this.intervalProbe.on("stats", (statsObj) => {
35 | this.enc_interval = statsObj.total_avg;
36 | });
37 | this.node.__cyclonNode.on("shuffleCompleted",(direction)=>{
38 | this.shuffleCount++;
39 | });
40 |
41 | this.node.__cyclonNode.on("shuffleError", (direction) => {
42 | this.errorCount++;
43 | });
44 |
45 | this.node.__cyclonNode.on("shuffleTimeout", (direction) => {
46 | this.timeoutCount++;
47 | });
48 | }
49 |
50 | _initStats(){
51 | this.neighborNames = [];
52 | this.shuffleCount = 0;
53 | this.timeoutCount = 0;
54 | this.errorCount = 0;
55 | this.enc_interval = 0;
56 | }
57 | }
58 |
59 | module.exports = NodeStatsProbe;
60 |
--------------------------------------------------------------------------------
/stats/StatsObjURLTagger.js:
--------------------------------------------------------------------------------
1 | let constants = require("../constants");
2 | /**
3 | * responsibility: tags stats objects with the url they should be sent to.
4 | */
5 | class StatsObjURLTagger {
6 | tagStatsObj(obj){
7 | switch (obj.event) {
8 | case constants.EVENTS.SEARCH_RELAY:
9 | this.__handleSearchRelay(obj);
10 | break;
11 | case constants.EVENTS.SEARCH_START:
12 | this.__handleSearchReq(obj);
13 | break;
14 | case constants.EVENTS.SEARCH_RESPOND:
15 | this.__handleSearchRes(obj);
16 | break;
17 | case constants.EVENTS.SEARCH_DISCARDED:
18 | this.__handleSearchDiscard(obj);
19 | break;
20 | case constants.EVENTS.SEARCH_REVISITED:
21 | this.__handleSearchRevisit(obj);
22 | break;
23 | case constants.EVENTS.NODE_STATS:
24 | this.__handleNodeStats(obj);
25 | break;
26 |
27 | }
28 | }
29 |
30 | __handleNodeStats(obj){
31 | let url = `${constants.STATS_HTTP_URL}/node_stats?json=${JSON.stringify(obj)}`;
32 | obj.url = url;
33 | }
34 | __handleSearchReq(obj){
35 | let url = `${constants.STATS_HTTP_URL}/search_started?id=${obj.id}&source_name=${obj.source_name}&query=${obj.query}`;
36 | obj.url = url;
37 | }
38 | __handleSearchRes(obj){
39 | let url = `${constants.STATS_HTTP_URL}/search_responded?id=${obj.id}&node_name=${obj.source_name}`;
40 | obj.url = url;
41 | }
42 | __handleSearchRelay(obj){
43 | let url = `${constants.STATS_HTTP_URL}/search_relayed?id=${obj.id}&node_name=${obj.source_name}&target_name=${obj.target_name}`;
44 | obj.url = url;
45 | }
46 | __handleSearchDiscard(obj){
47 | let url = `${constants.STATS_HTTP_URL}/search_discarded?id=${obj.id}&node_name=${obj.source_name}`;
48 | obj.url = url;
49 | }
50 | __handleSearchRevisit(obj){
51 | let url = `${constants.STATS_HTTP_URL}/search_revisited?id=${obj.id}&node_name=${obj.source_name}`;
52 | obj.url = url;
53 | }
54 | }
55 |
56 | module.exports = StatsObjURLTagger;
57 |
--------------------------------------------------------------------------------
/controllers/SearchRequest.js:
--------------------------------------------------------------------------------
1 | const NodeController = require("./NodeController");
2 | const constants = require("../constants");
3 | const uuid = require("uuid/v4");
4 | var cyclonRtc = require('cyclon.p2p-rtc-client');
5 |
6 |
7 | class SearchRequest extends NodeController{
8 | constructor(node,searchTerm,list){
9 | super(node);
10 | this.searchTerm = searchTerm;
11 | this.id = uuid();
12 | this.responses = [];
13 | this.list = list;
14 | }
15 |
16 | initiateSearch(){
17 | let neighborIds = this.node.getRandomSamplePointers();
18 | let packet = {};
19 | packet[constants.PACKET_FIELD.PACKET_ID] = this.id;
20 | packet[constants.PACKET_FIELD.PACKET_SOURCE] = this.node.__cyclonNode.createNewPointer();
21 | packet[constants.PACKET_FIELD.PACKET_TYPE] = constants.PACKET_TYPE.SEARCH_REQ;
22 | packet[constants.PACKET_FIELD.HOPS] = 1;
23 | packet[constants.PACKET_FIELD.QUERY]= this.searchTerm;
24 | packet[constants.PACKET_FIELD.LIST]= this.list;
25 |
26 | let stats_obj = {event:constants.EVENTS.SEARCH_START,id:this.id,
27 | source_name:this.node.name,query:this.searchTerm };
28 | this.emit("stats", stats_obj);
29 | // this.emit("stats", constants.EVENTS.SEARCH_START, this.id, this.node.name, this.searchTerm, neighborIds.length);
30 | for(let neighborId of neighborIds) {
31 | this.sendOutPacket(packet, neighborId);
32 |
33 | // let httpReq = new cyclonRtc.HttpRequestService();
34 | // httpReq.get(`http://localhost:3500/stats/search_started?id=${this.id}&source_name=${this.node.name}&query=${this.searchTerm}`);
35 | }
36 | if (neighborIds.length===0){
37 | for (let elem of this.node.listManager.getAllProximityLists(this.list)[0].getAllElements()){
38 | this.sendOutPacket(packet,elem.value);
39 | }
40 | }
41 | this.node.handledPacketIds.push(packet[constants.PACKET_FIELD.PACKET_ID]);
42 |
43 | }
44 |
45 | handlePacket(packet){
46 | if (packet[constants.PACKET_FIELD.PACKET_TYPE] !== constants.PACKET_TYPE.SEARCH_RES)
47 | return false;
48 | if (this.id !== packet[constants.PACKET_FIELD.REQUEST_ID])
49 | return false;
50 | console.info("received response for request: " + this.id +" response from: "+packet[constants.PACKET_FIELD.PACKET_SOURCE]);
51 | this.responses.push(packet);
52 | this.emit("search_result",packet);
53 | return true;
54 | }
55 | }
56 |
57 | module.exports = SearchRequest;
58 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | let Node = require("./node").Node;
2 | let faker = require("faker");
3 | let SearchRequest = require("./controllers/SearchRequest");
4 | let SearchResponder = require("./controllers/SearchResponder");
5 | let stringSimilarity = require("string-similarity");
6 |
7 |
8 |
9 | let node = new Node((channel)=>{
10 | console.info("connection received!");
11 | console.info(channel);
12 | },{});
13 | // let name = faker.name.firstName();
14 | // while (name.charAt(0) !== 'K') {
15 | // name = faker.name.firstName();
16 | // }
17 | let name = `${Math.floor(Math.random()*200)},${Math.floor(Math.random()*200)}`;
18 | node.registerList("list#name", (a, b) =>{
19 | let x1 = a.split(",")[0];
20 | let y1 = a.split(",")[1];
21 | let x2 = b.split(",")[0];
22 | let y2 = b.split(",")[1];
23 | return 1/Math.sqrt(Math.pow(Math.abs(x1-x2),2)+Math.pow(Math.abs(y1-y2),2));
24 | },0.2);
25 | node.setEntries("list#name", [name]);
26 | node.name= name;
27 | window.document.title = name;
28 | node.startNode();
29 |
30 | // let clientInfoService = new ClientInfoService(persistentStorage);
31 | let neighbourSet = node.__cyclonNode.getNeighbourSet();
32 |
33 |
34 |
35 | let currWindow = [];
36 |
37 | window.onload = function () {
38 | document.getElementById("name").innerText = name;
39 | };
40 |
41 | let response_ids = {};
42 | global.runTest = function () {
43 | console.info("running search");
44 | // let searchRequest = new SearchRequest(node, document.getElementById("new_name").value,"list#name");
45 | document.getElementById("results").innerHTML="";
46 | // response_ids = {};
47 | // searchRequest.on("search_result", (packet) => {
48 | // if (!response_ids[packet.packet_id]){
49 | // response_ids[packet.packet_id]= 1;
50 | // }else {
51 | // response_ids[packet.packet_id]++;
52 | // }
53 | // });
54 | // node.attachController(searchRequest);
55 | // node.statsRecorder.addEventEmitter(searchRequest);
56 | // searchRequest.initiateSearch();
57 | node.search("list#name", document.getElementById("new_name").value, 60, (value) => {
58 | console.info("called");
59 | let point = JSON.stringify(value);
60 | document.getElementById("results").innerHTML +=
61 | ` ${JSON.stringify(value)}
`;
62 | });
63 | };
64 |
65 | global.connectBtnClicked = function () {
66 | // console.info(document.getElementById("pointer").innerText);
67 | let pointer = JSON.parse(document.getElementById("pointer").value);
68 | node.connectToNode(pointer).then((rtcDataChannel) => {
69 |
70 | console.info("connected!");
71 | console.info(rtcDataChannel);
72 | });
73 | };
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/controllers/ProximityLinkBooster.js:
--------------------------------------------------------------------------------
1 | const NodeController = require("./NodeController");
2 | const constants = require("../constants");
3 |
4 | class ProximityLinkBooster extends NodeController{
5 | constructor(node,globalList){
6 | super(node);
7 | this._globalList = globalList;
8 | this._startSendTimer();
9 | }
10 |
11 | /**
12 | *
13 | * @param packet
14 | * @return {Boolean} true if packet could be handled, false if this controller can't handle the given packet
15 | */
16 | handlePacket(packet){
17 | if (packet[constants.PACKET_FIELD.PACKET_TYPE] !== constants.PACKET_TYPE.PROXIMITY_LINKS ||
18 | packet[constants.PACKET_FIELD.LIST !== this._globalList]) {
19 | return false;
20 | }
21 | this.node._handlePointerSet(packet[constants.PACKET_FIELD.POINTERS]);
22 | let entry = {key: packet[constants.PACKET_FIELD.ENTRY], value: packet[constants.PACKET_FIELD.PACKET_SOURCE]};
23 | this.node.listManager.addInboundElementToList(packet[constants.PACKET_FIELD.LIST], entry);
24 | return true;
25 | }
26 |
27 | _startSendTimer(){
28 | // return;
29 | // console.log("sending");
30 | // this._sendProximityLinks();
31 | let proxLists = this.node.listManager.getAllProximityLists(this._globalList);
32 | if (proxLists) {
33 | let randProxList = proxLists[Math.floor(Math.random() * proxLists.length)];
34 | if (randProxList.list.length>1) {
35 | let randomProximateNodePointer = randProxList.list[Math.floor(Math.random() * randProxList.list.length)].value;
36 | let packet = this._createPacket(randomProximateNodePointer, randProxList);
37 | this.sendOutPacket(packet, randomProximateNodePointer);
38 | }
39 | }
40 | setTimeout(()=>{this._startSendTimer()},15000);
41 | }
42 |
43 | _createPacket(targetPointer, proxList){
44 | let pointers = proxList.list.map((proximityListEntry)=>{
45 | return proximityListEntry.value;
46 | });
47 | pointers = pointers.filter((pointer)=>{
48 | if (pointer !== targetPointer){
49 | return true;
50 | }
51 | console.log("same!");
52 | return false;
53 | });
54 | let packet = {};
55 | packet[constants.PACKET_FIELD.PACKET_TYPE] = constants.PACKET_TYPE.PROXIMITY_LINKS;
56 | packet[constants.PACKET_FIELD.POINTERS] = pointers;
57 | packet[constants.PACKET_FIELD.LIST] = this._globalList;
58 | packet[constants.PACKET_FIELD.ENTRY] = proxList.referenceElement.key;
59 | packet[constants.PACKET_FIELD.PACKET_SOURCE] = this.node.__cyclonNode.createNewPointer();
60 | return packet;
61 | }
62 |
63 | }
64 |
65 | module.exports = ProximityLinkBooster;
--------------------------------------------------------------------------------
/controllers/SearchRelay.js:
--------------------------------------------------------------------------------
1 | const NodeController = require("./NodeController");
2 | const constants = require("../constants");
3 | var cyclonRtc = require('cyclon.p2p-rtc-client');
4 |
5 | class SearchRelay extends NodeController{
6 | constructor(node){
7 | super(node);
8 | this.handledPacketIds = [];
9 | this.maximumHops = 5;
10 | }
11 | handlePacket(packet){
12 | console.info("testing relay");
13 | if (packet[constants.PACKET_FIELD.PACKET_TYPE] !== constants.PACKET_TYPE.SEARCH_REQ) {
14 | return false;
15 | }
16 |
17 | packet[constants.PACKET_FIELD.HOPS] ++;
18 | let n = Math.pow(2, (this.maximumHops - packet[constants.PACKET_FIELD.HOPS])) ;
19 | if (n===1){
20 | return false;
21 | }else {
22 | let bestProxList = this.node.listManager.proxListWithClosestRefToElement(
23 | packet[constants.PACKET_FIELD.QUERY], packet[constants.PACKET_FIELD.LIST]);
24 | let randomEntries = this.node.__getRandomEntriesForList(packet[constants.PACKET_FIELD.LIST]);
25 | randomEntries = randomEntries.map((value => {
26 | return {key:value.key,value:value.pointer};
27 | }));
28 | let allEntries = bestProxList.getAllElements().concat(randomEntries);
29 | allEntries = allEntries.concat(this.node.listManager.getInboundListForGlobalList(packet[constants.PACKET_FIELD.LIST]));
30 | let sortedList = bestProxList.sortListOnProximityToElement(allEntries, {key: packet[constants.PACKET_FIELD.QUERY]});
31 |
32 | // let nearNodes = bestProxList.nearestNodesTo({key:packet[constants.PACKET_FIELD.QUERY]}, n);
33 | let nearNodes = sortedList.slice(0, 2);
34 | let refScore = bestProxList.proximityScoreComparedToRef(packet[constants.PACKET_FIELD.QUERY]);
35 |
36 | nearNodes = nearNodes.filter((value) => {
37 | if (value.score>=refScore){
38 | return true;
39 | }else{
40 | return false;
41 | }
42 | });
43 |
44 | console.info("near nodes: ");
45 | console.info(nearNodes);
46 | // let httpReq = new cyclonRtc.HttpRequestService();
47 | // httpReq.get(`http://localhost:3500/stats/search_relayed?id=${packet[constants.PACKET_FIELD.PACKET_ID]}&node_name=${this.node.name}`);
48 | for (let node of nearNodes) {
49 | let stats_obj = {event:constants.EVENTS.SEARCH_RELAY,id:packet[constants.PACKET_FIELD.PACKET_ID],
50 | source_name:this.node.name,target_name: node.key};
51 | this.emit("stats", stats_obj);
52 | this.sendOutPacket(packet, node.value);
53 | }
54 | return true;
55 | }
56 | }
57 | }
58 |
59 | module.exports = SearchRelay;
60 |
--------------------------------------------------------------------------------
/controllers/SearchResponder.js:
--------------------------------------------------------------------------------
1 | const NodeController = require("./NodeController");
2 | var cyclonRtc = require('cyclon.p2p-rtc-client');
3 | const constants = require("../constants");
4 | const uuid = require("uuid/v4");
5 |
6 | class SearchResponder extends NodeController{
7 | constructor(node){
8 | super(node);
9 | this.handledPacketIds = [];
10 | }
11 | handlePacket(packet){
12 | console.info("testing responder");
13 | if (packet[constants.PACKET_FIELD.PACKET_TYPE] !== constants.PACKET_TYPE.SEARCH_REQ) {
14 | return false;
15 | }
16 | // if (this.handledPacketIds.includes(packet[constants.PACKET_FIELD.PACKET_ID])) {
17 | // let stats_obj = {event:constants.EVENTS.SEARCH_REVISITED,id:packet[constants.PACKET_FIELD.PACKET_ID],source_name:this.node.name};
18 | // this.emit("stats", stats_obj);
19 | // return false;
20 | // }else{
21 | // if (this.handledPacketIds.length>200){
22 | // this.handledPacketIds = [];
23 | // }
24 | // this.handledPacketIds.push(packet[constants.PACKET_FIELD.PACKET_ID]);
25 | // }
26 |
27 | let response = this.__responseForPacket(packet);
28 | if (!response){
29 | return false;
30 | }
31 |
32 | this.__sendResponseFor(packet,response);
33 |
34 | let stats_obj = {event:constants.EVENTS.SEARCH_RESPOND,id:packet[constants.PACKET_FIELD.PACKET_ID],source_name:this.node.name};
35 | this.emit("stats", stats_obj);
36 | // let httpReq = new cyclonRtc.HttpRequestService();
37 | // httpReq.get(`http://localhost:3500/stats/search_responded?id=${packet[constants.PACKET_FIELD.PACKET_ID]}&node_name=${this.node.name}`);
38 | return true;
39 | }
40 |
41 | __responseForPacket(packet){
42 | let proxLists = this.node.listManager.getAllProximityLists(packet[constants.PACKET_FIELD.LIST]);
43 | for (let proxList of proxLists){
44 | let match = proxList.queryHit(packet[constants.PACKET_FIELD.QUERY]);
45 | if(match){
46 | console.info("query was hit");
47 | return {key: proxList.referenceElement.key,value:this.node.__cyclonNode.createNewPointer()};
48 | }
49 | }
50 | return undefined;
51 | }
52 |
53 | __sendResponseFor(packet,responseBody){
54 | console.info("search responder: handling packet");
55 | let response = {};
56 | response[constants.PACKET_FIELD.PACKET_ID] = uuid();
57 | response[constants.PACKET_FIELD.REQUEST_ID] = packet[constants.PACKET_FIELD.PACKET_ID];
58 | response[constants.PACKET_FIELD.PACKET_TYPE] = constants.PACKET_TYPE.SEARCH_RES;
59 | response[constants.PACKET_FIELD.BODY] = responseBody;
60 | this.node.sendObjectToNode(response,packet[constants.PACKET_FIELD.PACKET_SOURCE]);
61 | }
62 | }
63 |
64 |
65 | module.exports = SearchResponder;
66 |
--------------------------------------------------------------------------------
/stats/EncounterIntervalProbe.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require("events").EventEmitter;
2 | let constants = require("../constants");
3 | /**
4 | * Has the responsibility to gather and report number of shuffles between encounters of other nodes. This data
5 | * can possibly be used to measure network size.
6 | */
7 | class EncounterIntervalProbe extends EventEmitter{
8 | get node() {
9 | return this._node;
10 | }
11 |
12 | get sampleSize() {
13 | return this._sampleSize;
14 | }
15 |
16 | get encounterCounts() {
17 | return this._encounterCounts;
18 | }
19 | /**
20 | *
21 | * @param sampleSize number of nodes to check encounters for.
22 | * @param encounterCounts number of encounters to average and report at once
23 | */
24 | constructor(node,sampleSize,encounterCounts){
25 | super();
26 | this._node = node;
27 | this._sampleSize = sampleSize;
28 | this._encounterCounts = encounterCounts;
29 | this.samples= [];
30 | this.currentIndex= 0;
31 | this._gatherData();
32 | }
33 |
34 | _gatherData(){
35 | this._node.__cyclonNode.on("shuffleCompleted", (direction,pointer)=> {
36 | this.currentIndex++;
37 | if (this.currentIndex===1){
38 | this._initializeSamples();
39 | }else{
40 | this._updateSamples();
41 | this._checkFinishCriteria();
42 | }
43 | });
44 | }
45 | _checkFinishCriteria(){
46 | for (let sample of this.samples){
47 | if (sample.encounters.length!== this._encounterCounts){
48 | return false;
49 | }
50 | }
51 | // all samples have gathered 3 encounters, finish criteria triggered
52 | console.info(JSON.stringify(this.samples));
53 | let totalAvg = this.samples.map((value)=>{
54 | return (value.encounters[value.encounters.length-1]-1)/this._encounterCounts;
55 | }).reduce(((previousValue, currentValue) => {
56 | return previousValue+currentValue;
57 | }),0);
58 | let statsObj = {sample_size: this._sampleSize,interval_count: this._encounterCounts,
59 | total_avg: totalAvg,source_name: this._node.name};
60 | this.emit("stats",statsObj);
61 | this.currentIndex = 0;
62 | console.info(statsObj);
63 | }
64 |
65 |
66 | _updateSamples(){
67 | let neighbors = this._node.getRandomSamplePointers();
68 | let neighborIds = this._node.getRandomSampleIds();
69 | for (let sample of this.samples){
70 | if (neighborIds.includes(sample.id)){
71 | console.info("encountered");
72 | sample.encounters.push(this.currentIndex);
73 | }
74 | }
75 | }
76 |
77 | _initializeSamples(){
78 | let neighbors = this._node.getRandomSamplePointers();
79 | let size = Math.min(neighbors.length, this._sampleSize);
80 | this.samples=[];
81 | for (let i=0;i {
22 | return a + b;
23 | });
24 | listManager.addGlobalList("files", (a, b) => {
25 | return '';
26 | });
27 | }).to.throw(Error);
28 | })
29 |
30 | // it('should add neighbour to list', function () {
31 | // let listManager = new ListManager();
32 | // listManager.addGlobalList("sampleList", (a, b) => {
33 | // return a + b;
34 | // });
35 | // listManager.addEntry("sampleList", {key: "dkf", value: "dff"});
36 | // listManager.addNeighbor("sampleList", {key: {obj: 'hello'}, value: "34fd344"});
37 | // let lists = listManager.getAllProximityLists("sampleList");
38 | // assert.equal(lists.length, 1);
39 | // assert.equal(lists[0].getElementCount(), 1);
40 | // });
41 |
42 | it('should remove a proximity list from global list', function () {
43 | let listManager = new ListManager();
44 | listManager.addGlobalList("sampleList", (a, b) => {
45 | return a + b;
46 | });
47 | listManager.addEntry("sampleList", {key: "dkf", value: "dff"});
48 | listManager.removeEntry("sampleList", {key: "dkf"});
49 | assert.equal(listManager.getAllProximityLists("sampleList").length, 0);
50 | });
51 |
52 | // it('should maintain multiple lists', function () {
53 | // let listManager = new ListManager();
54 | // listManager.proximityListSize=12;
55 | // listManager.addGlobalList("Files", (a, b) => {
56 | // return a + b;
57 | // });
58 | // listManager.addGlobalList("Names", (a, b) => {
59 | // return a + b;
60 | // });
61 | // listManager.addEntry("Files", {key: "file1.jpg", value: "23adf324"});
62 | // listManager.addEntry("Files", {key: "file2.jpg", value: "23adf324"});
63 | // listManager.addEntry("Names", {key: "Jack", value: "23adf324"});
64 | //
65 | // listManager.addNeighbor("Files", {key:"f.jpg"});
66 | // listManager.addNeighbor("Files", {key:"b.txt"});
67 | // listManager.addNeighbor("Names", {key:"Jo"});
68 | // listManager.addNeighbor("Names", {key:"Jimmy"});
69 | // listManager.addNeighbor("Names", {key:"Jacky"});
70 | //
71 | // let fileCounts = listManager.getAllProximityLists("Files").map((value) => {
72 | // return value.getElementCount();
73 | // });
74 | // let nameCounts = listManager.getAllProximityLists("Names").map((value) => {
75 | // return value.getElementCount();
76 | // });
77 | //
78 | // assert.equal(fileCounts.reduce((previous, current) => {
79 | // return previous + current
80 | // }, 0), 4);
81 | // assert.equal(nameCounts.reduce((previous, current) => {
82 | // return previous + current
83 | // }, 0), 3);
84 | //
85 | // });
86 | });
87 |
--------------------------------------------------------------------------------
/proximity/ProximityList.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require("events").EventEmitter;
2 | const constants = require("../constants");
3 | var cyclonRtc = require('cyclon.p2p-rtc-client');
4 | /**
5 | * Maintains a list of most similar items to a given reference element, the similarity between elements is determined
6 | * by the proximity function (a,b) => c, higher values of c mean that a and b are more similar
7 | */
8 | class ProximityList extends EventEmitter{
9 | /**
10 | *
11 | * @param maximumListSize
12 | * @param referenceElement
13 | * @param proximityFunc (a,b) => float n, 0 this.minResponseScore;
37 | }
38 |
39 |
40 | perfectMatchForElement(key) {
41 | for (let e of [this.referenceElement, ...this.list]) {
42 | if (JSON.stringify(e.key) === JSON.stringify(key)) {
43 | return e;
44 | }
45 | }
46 | return undefined;
47 | }
48 |
49 | /**
50 | * Sorts a list of external elements according to how close they are to external element
51 | * @param extList
52 | * @param extElem
53 | */
54 | sortListOnProximityToElement(extList, extElem) {
55 | for (let elem of extList) {
56 | let score = this.proximityFunc(elem.key, extElem.key);
57 | elem.score = score;
58 | }
59 | return extList.sort((a, b) => {
60 | if (a.score > b.score) {
61 | return -1;
62 | } else if (a.score === b.score) {
63 | return 0;
64 | } else {
65 | return 1;
66 | }
67 | });
68 | }
69 |
70 |
71 | nearestNodesTo(element, count) {
72 | let scores = this.list.map((value => {
73 | let score = this.proximityFunc(element, value);
74 | return {score, elem: value};
75 | }));
76 | scores = scores.sort((a, b) => {
77 | if (a.score > b.score) {
78 | return -1;
79 | } else if (a.score === b.score) {
80 | return 0;
81 | } else {
82 | return 1;
83 | }
84 | });
85 | return scores.slice(0, count).map((value => {
86 | return value.elem;
87 | }));
88 | }
89 |
90 |
91 | addElement(element) {
92 | if (!element.key && element.key!==0){
93 | // bad element -> no key
94 | throw Error("Proximity List: element to be added should have a key");
95 | }
96 | element.proximityScore = this.proximityFunc(this.referenceElement.key, element.key).toFixed(3);
97 | this._putElement(element);
98 | this.list = this.list.slice(0, this.maximumListSize);
99 | // console.info("new list: " + this.list);
100 | }
101 |
102 | addElements(elements) {
103 | for (let elem of elements) {
104 | this.addElement(elem);
105 | }
106 | }
107 |
108 | // addInboundElement(element){
109 | // this.inboundList.unshift(element);
110 | // if (this.inboundList.length >= this.inboundListSize) {
111 | // this.inboundList.pop();
112 | // }
113 | // }
114 |
115 | /**
116 | *
117 | * @return {null|*} most similar element object or null if list is empty
118 | */
119 | getMostSimilarElement() {
120 | if (this.list.length > 0) {
121 | return this.list[0];
122 | }
123 | return null;
124 | }
125 |
126 | /**
127 | * Put the element in the list according to its score
128 | * @param element
129 | * @private
130 | */
131 | _putElement(element) {
132 | for (let i = 0; i < this.list.length; i++) {
133 | if (this.list[i].proximityScore < element.proximityScore) {
134 | this.list.splice(i, 0, element);
135 | return true;
136 | }
137 | }
138 | this.list.push(element);
139 | return false;
140 | }
141 |
142 | getKeys(){
143 | return this.list.map((value => {
144 | return value.key;
145 | }));
146 | }
147 |
148 | getAllElements() {
149 | return this.list;
150 | }
151 |
152 | getElementCount() {
153 | return this.list.length;
154 | }
155 |
156 |
157 | }
158 |
159 | // let proxList = new ProximityList(4,{key: 8},(a,b)=>{return 1/Math.abs(a-b)})
160 | // for (let i=0;i<20;i++){
161 | // proxList.addElement({key: i,value:i});
162 | // }
163 | // console.log(proxList);
164 |
165 | module.exports = ProximityList;
166 |
167 |
--------------------------------------------------------------------------------
/proximity/ListManager.js:
--------------------------------------------------------------------------------
1 | let ProximityList = require("./ProximityList");
2 |
3 | /**
4 | * List manager works with entries of type {key,value}
5 | */
6 | class ListManager {
7 | constructor() {
8 | this.proximityListSize = 6;
9 | this.lists = [];
10 | this.inboundListSize = 5;
11 | }
12 |
13 | addGlobalList(globalList, proximityFunction, responseMinScore) {
14 | for (let list of this.lists) {
15 | if (list.listName === globalList) {
16 | throw new Error(`global list "${globalList}" exists already`);
17 | }
18 | }
19 | let newGlobalList =
20 | {listName: globalList,
21 | lists: [],
22 | proximityFunction: proximityFunction,
23 | inboundList: [],
24 | responseMinScore: responseMinScore};
25 | this.lists.push(newGlobalList);
26 |
27 | }
28 |
29 | addInboundElementToList(globalList,entry){
30 | if (!entry.key && entry.key!==0){
31 | // bad element -> no key
32 | throw Error("List Manager: entry to be added should have a key");
33 | }
34 | let list = this.getGlobalList(globalList);
35 | if (!list) {
36 | console.warn(entry);
37 | } else {
38 | list.inboundList.unshift(entry);
39 | if (list.inboundList.length >= this.inboundListSize) {
40 | list.inboundList.pop();
41 | }
42 | }
43 | }
44 | getInboundListForGlobalList(globalList){
45 | let list = this.getGlobalList(globalList);
46 | if (!list) {
47 | console.warn(entry);
48 | } else {
49 | return list.inboundList;
50 | }
51 | }
52 |
53 | /**
54 | *
55 | * @param globalList
56 | * @return list or undefined if the list doesnt exist
57 | */
58 | getGlobalList(globalList) {
59 | for (let list of this.lists){
60 | if (list.listName === globalList) {
61 | return list;
62 | }
63 | }
64 | return undefined;
65 | }
66 |
67 |
68 | removeEntry(globalList,entry){
69 | if (!entry.key && entry.key!==0){
70 | // bad element -> no key
71 | throw Error("List Manager: entry to be removed should have a key");
72 | }
73 | let list = this.getGlobalList(globalList);
74 | if (!list){
75 | console.warn(`tried to remove entry ${entry} from non-existent global list: ${globalList}`);
76 | return;
77 | }
78 | list.lists = list.lists.filter((value) => {
79 | return (value.referenceElement.key !== entry.key);
80 | });
81 | }
82 |
83 | addEntry(globalList, entry) {
84 | if (!entry.key && entry.key!==0){
85 | // bad element -> no key
86 | throw Error("List Manager: entry to be added should have a key");
87 | }
88 | let list = this.getGlobalList(globalList);
89 | if (!list) {
90 | console.warn(`tried to add entry ${entry} to non-existent global list: ${globalList}. Ignoring.`);
91 | console.warn(entry);
92 | } else {
93 | for (let l of list.lists){
94 | if (l.referenceElement.key === entry.key){
95 | console.warn(`a proximity list for entry ${entry} already exists.`);
96 | return;
97 | }
98 | }
99 | let proxList = new ProximityList(this.proximityListSize, entry, list.proximityFunction, list.responseMinScore);
100 | list.lists.push(proxList);
101 | }
102 | }
103 |
104 | removeAllRecordsFromAllLists(filterFunc){
105 | for (let globalList of this.lists) {
106 | for (let proxList of globalList.lists) {
107 | proxList.list = proxList.list.filter(filterFunc);
108 | }
109 | }
110 | }
111 |
112 | addElementToAllProximityLists(globalList, element) {
113 | let list = this.getGlobalList(globalList);
114 | if (!list) {
115 | console.warn(`tried to add list element ${element} to non-existent global list: ${globalList}`);
116 | return undefined;
117 | }
118 | for (let l of list.lists){
119 | l.addElement(element);
120 | }
121 | }
122 |
123 |
124 | getAllProximityLists(globalList) {
125 | let list = this.getGlobalList(globalList);
126 | if (!list) {
127 | return undefined;
128 | }
129 | return list.lists;
130 | }
131 |
132 |
133 | /**
134 | * @return return all entries in the form of {key,list}
135 | */
136 | getAllLocalEntries(){
137 | const allEntries = [];
138 | for (let list of this.lists){
139 | for (let proxList of list.lists){
140 | allEntries.push({list: list.listName, key: proxList.referenceElement.key});
141 | }
142 | }
143 | return allEntries
144 | }
145 |
146 | /**
147 | * returns the proximity list that has the closest reference element key to in
148 | */
149 | proxListWithClosestRefToElement(key,globalList){
150 | let proxLists = this.getAllProximityLists(globalList);
151 | let bestProxList = undefined;
152 | let bestScore = Number.NEGATIVE_INFINITY;
153 | for (let proxList of proxLists){
154 | let score = proxList.proximityScoreComparedToRef(key);
155 | if (score>bestScore){
156 | bestScore = score;
157 | bestProxList = proxList;
158 | }
159 | }
160 | return bestProxList;
161 | }
162 |
163 | }
164 |
165 |
166 | module.exports = ListManager;
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # MeshP2P
6 | #### Create P2P apps between browsers
7 |
8 | MeshP2P is a framework that allows bootstrapping the development of a distributed application over web browsers using WebRTC.
9 | It allows for discovery of other peers without requiring a central server. This is done through regular exchange of information between peers.
10 |
11 |
12 | ## Usage
13 | ```
14 | npm install meshp2p --save
15 | ```
16 |
17 | #### Importing in your js code
18 | ```
19 | const Node = require("meshp2p").Node;
20 | ```
21 |
22 | ## API
23 |
24 |
25 | ### Node Constructor
26 | As a minimum a callback for incoming rtc connections, and an array of signalling servers should be provided to the Node constructor.
27 |
28 | ##### Arguments
29 | 1. InboundCb (func): a function that is called when another peer makes a connection to this node. The rtcDataChannel will provided
30 | as an argument to the callback function.
31 | 2. Options (Obj): parameters, DEFAULT_SIGNALLING_SERVERS is required
32 |
33 | ##### Example
34 | Assuming two signalling servers are running locally on ports 12345 and 12346:
35 | ```
36 | node = new Node((rtcDataChannel)=>{
37 | // do something with inbound connection
38 | },{DEFAULT_SIGNALLING_SERVERS:[
39 | {
40 | "socket": {
41 | "server": "http://127.0.0.1:12345"
42 | },
43 | "signallingApiBase": "http://127.0.0.1:12345"
44 | },
45 | {
46 | "socket": {
47 | "server": "http://127.0.0.1:12346"
48 | },
49 | "signallingApiBase": "http://127.0.0.1:12346"
50 | }
51 | ]});
52 | ```
53 |
54 | See [Signalling Servers](#signalling-servers) on how to start signalling servers.
55 |
56 | ### registerList
57 |
58 | MeshP2P allows for multiple lists to exist in the network, and each node is able to register multiple entries in each list.
59 | Make a global list in the network using registerList.
60 |
61 | ##### Arguments
62 | 1. list (string): The name of the list
63 | 2. proximityFunction (func): The proximity function specifies how closeness between nodes is defined.
64 | It has the form (entry1, entry2) => float. The higher returned score from proximity function means the
65 | two entries are more identical, and a score of 0 means least identical.
66 | 3. responseMinScore (float): this is the minimum closeness score between entries to consider it a hit, and a node would respond to the query.
67 |
68 | ##### Returns
69 |
70 | Void.
71 |
72 | ##### Example 1
73 |
74 | Consider a network with a list of names of peers. In this case it's natural to consider closeness between entries to be string similarity:
75 |
76 |
77 | ```
78 | registerList("list#name", (str1, str2) => {return stringSimilarity(str1,str2)}, 0.7)
79 | ```
80 |
81 | ##### Example 2
82 |
83 | Consider a network of peers with each peer having a coordinate in 2D space. In this case one can define the closeness to be inverse of their euclidean distance, and a distance of less than 4 to consider a hit:
84 |
85 | ```
86 | registerList("list#coordinates", (entry1,entry2) => {return 1/euclideanDist(entry1,entry2)}, 1/4)
87 | ```
88 |
89 | ### setEntries
90 |
91 | Set the entries for the current node in the specified network list.
92 |
93 | ##### Arguments
94 | 1. list (string): The global list
95 | 2. entries (object[]): the entries for this node
96 |
97 | ##### Returns
98 | Void.
99 |
100 | ##### Examples
101 | ```
102 | setEntries("list#names", ["Jack"])
103 | ```
104 |
105 | ```
106 | setEntries("list#coordinates",[{x: 3,y:12}])
107 | ```
108 |
109 | ### Search
110 |
111 | Search the network.
112 |
113 | ##### Arguments
114 | 1. list(string): The global list to search
115 | 2. query(obj): The query. The query should have the form of list entry, and will be fed to the provided proximityFunction provided.
116 | 3. timeout(int): Number of seconds to wait for responses from the network. After that, the resources are freed and responses for this query aren't handled.
117 | 4. searchResultCallback(func): The callback is called each time a response is received from the network. The response is passed to the callback function and has the form: {key,value}. key is the entry that caused a match, and the value is the nodePointer of the peer that has responded to the query.
118 |
119 | ##### Examples
120 | ```
121 | search("list#names", "jacky", 60, (response)=>{
122 | // do something with the response
123 | });
124 | ```
125 |
126 | ```
127 | search("list#coordinates", {x:2,y:2}, 60, (response)=>{
128 | // do something with the response
129 | });
130 | ```
131 |
132 | ### connectToNode
133 |
134 | ##### Arguments
135 | nodePointer(nodePointerObj): The node pointer of the peer to connect to.
136 |
137 | ##### Returns
138 | A promise that is resolved with an rtcDataChannel (https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel) when successfully connected to the target peer.
139 |
140 | ##### Examples
141 |
142 | ```
143 | node.search("list#names", "jacky", 60, (response)=>{
144 | node.connectToNode(response.value).then(rtcDataChannel)=>{
145 | rtcDataChannel.send("hello"); })
146 | });
147 | ```
148 |
149 | ### startNode
150 | Starts the node. Start node only after specifying the global list and the node's entries in the list.
151 |
152 | ##### Arguments
153 | None
154 |
155 |
156 | ## Signalling Servers
157 |
158 | WebRTC requires the use of signalling servers so that peers can negotiate for a connection. Signalling servers are provided as part of MeshP2P
159 | and can be easily started using:
160 | ```
161 | npm run server -- 12345
162 | ```
163 | This runs a signalling server on port:12345
164 |
165 | Peers in the network should have access to at least one signalling server, and this should be specified in the node constructor when
166 | creating peers.
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/thirdparty/adapter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
3 | *
4 | * Use of this source code is governed by a BSD-style license
5 | * that can be found in the LICENSE file in the root of the source
6 | * tree.
7 | */
8 |
9 | /* More information about these options at jshint.com/docs/options */
10 | /* global mozRTCIceCandidate, mozRTCPeerConnection,
11 | mozRTCSessionDescription, webkitRTCPeerConnection */
12 | /* exported trace */
13 |
14 | 'use strict';
15 |
16 | var RTCPeerConnection = null;
17 | var getUserMedia = null;
18 | var attachMediaStream = null;
19 | var reattachMediaStream = null;
20 | var webrtcDetectedBrowser = null;
21 | var webrtcDetectedVersion = null;
22 |
23 | function trace(text) {
24 | // This function is used for logging.
25 | if (text[text.length - 1] === '\n') {
26 | text = text.substring(0, text.length - 1);
27 | }
28 | console.log((window.performance.now() / 1000).toFixed(3) + ': ' + text);
29 | }
30 |
31 | function maybeFixConfiguration(pcConfig) {
32 | if (!pcConfig) {
33 | return;
34 | }
35 | for (var i = 0; i < pcConfig.iceServers.length; i++) {
36 | if (pcConfig.iceServers[i].hasOwnProperty('urls')) {
37 | pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls;
38 | delete pcConfig.iceServers[i].urls;
39 | }
40 | }
41 | }
42 |
43 | if (navigator.mozGetUserMedia) {
44 | console.log('This appears to be Firefox');
45 |
46 | webrtcDetectedBrowser = 'firefox';
47 |
48 | webrtcDetectedVersion =
49 | parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
50 |
51 | // The RTCPeerConnection object.
52 | RTCPeerConnection = function (pcConfig, pcConstraints) {
53 | // .urls is not supported in FF yet.
54 | maybeFixConfiguration(pcConfig);
55 | return new mozRTCPeerConnection(pcConfig, pcConstraints);
56 | };
57 |
58 | // The RTCSessionDescription object.
59 | window.RTCSessionDescription = mozRTCSessionDescription;
60 |
61 | // The RTCIceCandidate object.
62 | window.RTCIceCandidate = mozRTCIceCandidate;
63 |
64 | // getUserMedia shim (only difference is the prefix).
65 | // Code from Adam Barth.
66 | getUserMedia = navigator.mozGetUserMedia.bind(navigator);
67 | navigator.getUserMedia = getUserMedia;
68 |
69 | // Creates ICE server from the URL for FF.
70 | window.createIceServer = function (url, username, password) {
71 | var iceServer = null;
72 | var urlParts = url.split(':');
73 | if (urlParts[0].indexOf('stun') === 0) {
74 | // Create ICE server with STUN URL.
75 | iceServer = {
76 | 'url': url
77 | };
78 | } else if (urlParts[0].indexOf('turn') === 0) {
79 | if (webrtcDetectedVersion < 27) {
80 | // Create iceServer with turn url.
81 | // Ignore the transport parameter from TURN url for FF version <=27.
82 | var turnUrlParts = url.split('?');
83 | // Return null for createIceServer if transport=tcp.
84 | if (turnUrlParts.length === 1 ||
85 | turnUrlParts[1].indexOf('transport=udp') === 0) {
86 | iceServer = {
87 | 'url': turnUrlParts[0],
88 | 'credential': password,
89 | 'username': username
90 | };
91 | }
92 | } else {
93 | // FF 27 and above supports transport parameters in TURN url,
94 | // So passing in the full url to create iceServer.
95 | iceServer = {
96 | 'url': url,
97 | 'credential': password,
98 | 'username': username
99 | };
100 | }
101 | }
102 | return iceServer;
103 | };
104 |
105 | window.createIceServers = function (urls, username, password) {
106 | var iceServers = [];
107 | // Use .url for FireFox.
108 | for (var i = 0; i < urls.length; i++) {
109 | var iceServer =
110 | window.createIceServer(urls[i], username, password);
111 | if (iceServer !== null) {
112 | iceServers.push(iceServer);
113 | }
114 | }
115 | return iceServers;
116 | };
117 |
118 | // Attach a media stream to an element.
119 | attachMediaStream = function (element, stream) {
120 | console.log('Attaching media stream');
121 | element.mozSrcObject = stream;
122 | };
123 |
124 | reattachMediaStream = function (to, from) {
125 | console.log('Reattaching media stream');
126 | to.mozSrcObject = from.mozSrcObject;
127 | };
128 |
129 | } else if (navigator.webkitGetUserMedia) {
130 | console.log('This appears to be Chrome');
131 |
132 | webrtcDetectedBrowser = 'chrome';
133 | // Temporary fix until crbug/374263 is fixed.
134 | // Setting Chrome version to 999, if version is unavailable.
135 | var result = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
136 | if (result !== null) {
137 | webrtcDetectedVersion = parseInt(result[2], 10);
138 | } else {
139 | webrtcDetectedVersion = 999;
140 | }
141 |
142 | // Creates iceServer from the url for Chrome M33 and earlier.
143 | window.createIceServer = function (url, username, password) {
144 | var iceServer = null;
145 | var urlParts = url.split(':');
146 | if (urlParts[0].indexOf('stun') === 0) {
147 | // Create iceServer with stun url.
148 | iceServer = {
149 | 'url': url
150 | };
151 | } else if (urlParts[0].indexOf('turn') === 0) {
152 | // Chrome M28 & above uses below TURN format.
153 | iceServer = {
154 | 'url': url,
155 | 'credential': password,
156 | 'username': username
157 | };
158 | }
159 | return iceServer;
160 | };
161 |
162 | // Creates iceServers from the urls for Chrome M34 and above.
163 | window.createIceServers = function (urls, username, password) {
164 | var iceServers = [];
165 | if (webrtcDetectedVersion >= 34) {
166 | // .urls is supported since Chrome M34.
167 | iceServers = {
168 | 'urls': urls,
169 | 'credential': password,
170 | 'username': username
171 | };
172 | } else {
173 | for (var i = 0; i < urls.length; i++) {
174 | var iceServer =
175 | window.createIceServer(urls[i], username, password);
176 | if (iceServer !== null) {
177 | iceServers.push(iceServer);
178 | }
179 | }
180 | }
181 | return iceServers;
182 | };
183 |
184 | // The RTCPeerConnection object.
185 | RTCPeerConnection = function (pcConfig, pcConstraints) {
186 | // .urls is supported since Chrome M34.
187 | if (webrtcDetectedVersion < 34) {
188 | maybeFixConfiguration(pcConfig);
189 | }
190 | return new webkitRTCPeerConnection(pcConfig, pcConstraints);
191 | };
192 |
193 | // Get UserMedia (only difference is the prefix).
194 | // Code from Adam Barth.
195 | getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
196 | navigator.getUserMedia = getUserMedia;
197 |
198 | // Attach a media stream to an element.
199 | attachMediaStream = function (element, stream) {
200 | if (typeof element.srcObject !== 'undefined') {
201 | element.srcObject = stream;
202 | } else if (typeof element.mozSrcObject !== 'undefined') {
203 | element.mozSrcObject = stream;
204 | } else if (typeof element.src !== 'undefined') {
205 | element.src = URL.createObjectURL(stream);
206 | } else {
207 | console.log('Error attaching stream to element.');
208 | }
209 | };
210 |
211 | reattachMediaStream = function (to, from) {
212 | to.src = from.src;
213 | };
214 | } else {
215 | console.log('Browser does not appear to be WebRTC-capable');
216 | }
--------------------------------------------------------------------------------
/node.js:
--------------------------------------------------------------------------------
1 | var cyclon = require('cyclon.p2p');
2 | var cyclonRtc = require('cyclon.p2p-rtc-client');
3 | var cyclonRtcComms = require('cyclon.p2p-rtc-comms');
4 | var Utils = require("cyclon.p2p-common");
5 | let SearchResponder = require("./controllers/SearchResponder");
6 | let SearchRelay = require("./controllers/SearchRelay");
7 | let SearchRequest = require("./controllers/SearchRequest");
8 | const ListManager = require("./proximity/ListManager");
9 | let StatsRecorder = require("./stats/HTTPStatsRecorder");
10 | var EventEmitter = require("events").EventEmitter;
11 | let NodeStatsProbe = require("./stats/NodeStatsProbe");
12 | let ProximityLinkChangePrope = require("./stats/ProximityLinkChangeProbe");
13 | const constants = require("./constants");
14 | let ProximityLinkBooster = require("./controllers/ProximityLinkBooster");
15 |
16 | let logger = console;
17 |
18 | class Node extends EventEmitter{
19 | constructor(inboundConnectionCallback,
20 | {
21 | NEIGHBOR_SIZE= 7,
22 | SHUFFLE_SIZE= 3,
23 | TICK_INTERVAL= 20000,
24 | DEFAULT_SIGNALLING_SERVERS= [
25 | {
26 | "socket": {
27 | "server": "http://localhost:12345"
28 | },
29 | "signallingApiBase": "http://localhost:12345"
30 | },
31 | {
32 | "socket": {
33 | "server": "http://localhost:12346"
34 | },
35 | "signallingApiBase": "http://localhost:12346"
36 | }
37 | ],
38 | DEFAULT_BATCHING_DELAY_MS= 300,
39 | DEFAULT_ICE_SERVERS= [
40 | // The public Google STUN server
41 | {urls: ['stun:stun.l.google.com:19302']},
42 | ],
43 | DEFAULT_CHANNEL_STATE_TIMEOUT_MS= 30000,
44 | DEFAULT_SIGNALLING_SERVER_RECONNECT_DELAY_MS= 5000,
45 | ANALYTICS= false
46 | }
47 | ) {
48 | super();
49 | this.inboundCb = inboundConnectionCallback;
50 |
51 | this._config = {};
52 | //how to do following assignments in one statement?
53 | this._config.NEIGHBOR_SIZE= NEIGHBOR_SIZE;
54 | this._config.SHUFFLE_SIZE= SHUFFLE_SIZE;
55 | this._config.TICK_INTERVAL= TICK_INTERVAL;
56 | this._config.DEFAULT_SIGNALLING_SERVERS= DEFAULT_SIGNALLING_SERVERS;
57 | this._config.DEFAULT_BATCHING_DELAY_MS= DEFAULT_BATCHING_DELAY_MS;
58 | this._config.DEFAULT_ICE_SERVERS= DEFAULT_ICE_SERVERS;
59 | this._config.DEFAULT_CHANNEL_STATE_TIMEOUT_MS= DEFAULT_CHANNEL_STATE_TIMEOUT_MS;
60 | this._config.DEFAULT_SIGNALLING_SERVER_RECONNECT_DELAY_MS= DEFAULT_SIGNALLING_SERVER_RECONNECT_DELAY_MS;
61 | this._config.ANALYTICS = ANALYTICS;
62 |
63 | this.__controllers = [];
64 | this.listManager = new ListManager();
65 | this.name = '';
66 | this.__initCyclonNode();
67 | this.__initSearchControllers();
68 | this.proximityLinkBooster = new ProximityLinkBooster(this,"list#name");
69 | this.__controllers.push(this.proximityLinkBooster);
70 | if (this._config.ANALYTICS) {
71 | this.statsRecorder = new StatsRecorder();
72 | this.statsProbe = new NodeStatsProbe(this, 4000);
73 | this.linkChangeProbe = new ProximityLinkChangePrope(this);
74 | this.__addEventListeners();
75 | }
76 |
77 | this.__setupConnectionListener();
78 | this.handledPacketIds = [];
79 | }
80 |
81 | /**
82 | * Register a global list on this node
83 | * @param list
84 | * @param proximityFunction (a,b)->float 0 to 1, 1 being identical and 0 least similar
85 | *
86 | * @param responseMinScore
87 | */
88 | registerList(list,proximityFunction, responseMinScore){
89 | this.listManager.addGlobalList(list, proximityFunction, responseMinScore);
90 | }
91 |
92 | /**
93 | * Set the entries for the global list
94 | * @param list
95 | * @param entries
96 | */
97 | setEntries(list,entries){
98 | for (let entry of entries) {
99 | this.listManager.addEntry(list,{key:entry});
100 | }
101 |
102 | }
103 |
104 | /**
105 | * Search the global list for the object , searchResultCallback is called with the corresponding result
106 | * every time a response is received.
107 | *
108 | * @param list
109 | * @param query
110 | * @param timeout seconds after search request expires
111 | * @param searchResultCallback
112 | */
113 | search(list,query,timeout=60,searchResultCallback){
114 | let searchRequest = new SearchRequest(this, query,list);
115 | searchRequest.on("search_result", (packet) => {
116 | searchResultCallback(packet.body);
117 | });
118 | this.attachController(searchRequest);
119 | setTimeout(() => {
120 | this._removeController(searchRequest);
121 | }, timeout * 1000);
122 | if (this._config.ANALYTICS) {
123 | this.statsRecorder.addEventEmitter(searchRequest);
124 | }
125 | searchRequest.initiateSearch();
126 | }
127 |
128 | /**
129 | * Connects to the node specified by nodePointer and returns an rtc data channel
130 | * @param nodePointer
131 | */
132 | async connectToNode(nodePointer){
133 | let channel = await this.rtc.openChannel("data", nodePointer);
134 | return channel.rtcDataChannel;
135 | }
136 |
137 |
138 |
139 | startNode(){
140 | // this.__cyclonNode.on("shuffleCompleted",(direction)=>{
141 | // console.info("shuffle completed");
142 | // });
143 |
144 | this.__cyclonNode.on("shuffleError", (direction) => {
145 | console.error("shuffle error");
146 | });
147 |
148 | this.__cyclonNode.on("shuffleTimeout", (direction) => {
149 | console.error("shuffle timeout");
150 | });
151 |
152 | console.info("starting node");
153 | this.__cyclonNode.start();
154 | console.info(this.__cyclonNode.createNewPointer());
155 | this.__setupHandlerForNewRandomNeighborSet();
156 | this.__listenForPackets();
157 | }
158 |
159 |
160 | __setupConnectionListener(){
161 | let self = this;
162 | this.rtc.onChannel("data", function (data) {
163 | self.inboundCb(data.rtcDataChannel);
164 | });
165 | }
166 |
167 | __addEventListeners(){
168 | for (let c of this.__controllers) {
169 | this.statsRecorder.addEventEmitter(c);
170 | }
171 |
172 | this.statsRecorder.addEventEmitter(this);
173 | this.statsRecorder.addEventEmitter(this.statsProbe);
174 | }
175 |
176 | __initSearchControllers() {
177 | let searchResponder = new SearchResponder(this);
178 | this.attachController(searchResponder);
179 | let searchRelay = new SearchRelay(this);
180 | this.attachController(searchRelay);
181 | }
182 |
183 |
184 | __initCyclonNode() {
185 | let self = this;
186 | let persistentStorage = sessionStorage;
187 | let inMemoryStorage = Utils.newInMemoryStorage();
188 | let timingService = new cyclonRtc.TimingService();
189 |
190 | //level 5
191 | let signallingServerService = new cyclonRtc.StaticSignallingServerService(this._config.DEFAULT_SIGNALLING_SERVERS);
192 | let socketFactory = new cyclonRtc.SocketFactory();
193 | let signallingServerSelector = new cyclonRtc.SignallingServerSelector(signallingServerService, persistentStorage, timingService, this._config.DEFAULT_SIGNALLING_SERVER_RECONNECT_DELAY_MS);
194 |
195 |
196 | //level4
197 | // let rtcObjectFactory = new cyclonRtc.AdapterJsRTCObjectFactory(logger);
198 | let rtcObjectFactory = new cyclonRtc.NativeRTCObjectFactory(logger);
199 | let signallingSocket = new cyclonRtc.RedundantSignallingSocket(signallingServerService, socketFactory, logger, Utils.asyncExecService(), signallingServerSelector);
200 | let httpRequestService = new cyclonRtc.HttpRequestService();
201 |
202 |
203 | //level3
204 | let signallingService = new cyclonRtc.SocketIOSignallingService(signallingSocket, logger, httpRequestService, persistentStorage);
205 | let peerConnectionFactory = new cyclonRtc.PeerConnectionFactory(rtcObjectFactory, logger, this._config.DEFAULT_ICE_SERVERS, this._config.DEFAULT_CHANNEL_STATE_TIMEOUT_MS);
206 |
207 |
208 | //level 2
209 | let iceCandidateBatchingSignalling = new cyclonRtc.IceCandidateBatchingSignallingService(Utils.asyncExecService(),
210 | signallingService, this._config.DEFAULT_BATCHING_DELAY_MS);
211 | let channelFactory = new cyclonRtc.ChannelFactory(peerConnectionFactory, iceCandidateBatchingSignalling, logger,this._config.DEFAULT_CHANNEL_STATE_TIMEOUT_MS);
212 | let shuffleStateFactory = new cyclonRtcComms.ShuffleStateFactory(logger, Utils.asyncExecService());
213 |
214 |
215 | // level 1
216 | this.rtc = new cyclonRtc.RTC(iceCandidateBatchingSignalling, channelFactory);
217 | this.comms = new cyclonRtcComms.WebRTCComms(this.rtc, shuffleStateFactory, logger,["meshp2p"]);
218 | this.bootStrap = new cyclonRtcComms.SignallingServerBootstrap(signallingSocket, httpRequestService,["meshp2p"]);
219 |
220 |
221 | // level 0
222 |
223 | this.__cyclonNode = cyclon.builder(this.comms, this.bootStrap)
224 | .withNumNeighbours(this._config.NEIGHBOR_SIZE)
225 | .withMetadataProviders({
226 | "clientInfo": () => {
227 | return this.listManager.getAllLocalEntries();
228 | },
229 | }
230 | )
231 | .withShuffleSize(this._config.SHUFFLE_SIZE)
232 | .withTickIntervalMs(this._config.TICK_INTERVAL)
233 | .build();
234 |
235 |
236 | }
237 |
238 |
239 | /**
240 | *
241 | * @param nodePointers these are pointers defined in cyclon.p2p
242 | * @private
243 | */
244 | __extractListEntriesFromPointers(nodePointers){
245 | let listEntries = [];
246 | for (let pointer of nodePointers){
247 | let entries = pointer["metadata"]["clientInfo"].map((value) => {
248 | return {key:value.key ,list:value.list ,pointer:pointer}
249 | });
250 | listEntries.push(...entries);
251 | }
252 | return listEntries;
253 | }
254 |
255 | __extractNodeIdsFromPointers(nodePointers){
256 | let nodeIds = [];
257 | for (let pointer of nodePointers){
258 | if (!nodeIds.includes(pointer.id)){
259 | nodeIds.push(pointer.id);
260 | }
261 | }
262 | return nodeIds;
263 | }
264 |
265 | __getRandomEntriesForList(list){
266 | let randomPointers = this.getRandomSamplePointers();
267 | let randomEntries = this.__extractListEntriesFromPointers(randomPointers);
268 | return randomEntries.filter((value => {
269 | if (value.list === list) {
270 | return true;
271 | } else {
272 | return false;
273 | }
274 | }));
275 | }
276 |
277 | __setupHandlerForNewRandomNeighborSet(){
278 | this.__cyclonNode.on("shuffleCompleted", (direction,pointer)=> {
279 | console.info(`${direction} shuffle complete. ${JSON.stringify(pointer)}`);
280 | let namesProxList = this.listManager.getAllProximityLists("list#name")[0];
281 | let beforeKeys = namesProxList.getAllElements().map((value) => {
282 | return value.key;
283 | });
284 | let pointerSet = this.getRandomSamplePointers();
285 | this._handlePointerSet(pointerSet);
286 | this.__sendNeighborsToStatsServer();
287 | let afterKeys = namesProxList.getAllElements().map((value) => {
288 | return value.key;
289 | });
290 | this.emit("neighbors_updated",beforeKeys,afterKeys);
291 | });
292 | }
293 |
294 | _handlePointerSet(pointerSet){
295 | let entries = this.__extractListEntriesFromPointers(pointerSet);
296 | let nodeIds = this.__extractNodeIdsFromPointers(pointerSet);
297 | for (let nodeId of nodeIds){
298 | this.__removeNeighbour({pointer: {id: nodeId}});
299 | }
300 | this.__incorporateNeighbourList(entries);
301 | }
302 |
303 |
304 | __incorporateNeighbourList(neighbourList) {
305 | for (let neighbor of neighbourList){
306 | let changed = this.listManager.addElementToAllProximityLists(neighbor.list,
307 | {key:neighbor.key,value:neighbor.pointer});
308 | }
309 | }
310 |
311 | __removeNeighbour(neighbour){
312 | let filterFunc = function (elem) {
313 | return (neighbour.pointer.id !== elem.value.id);
314 | };
315 | this.listManager.removeAllRecordsFromAllLists(filterFunc);
316 | }
317 |
318 | __sendNeighborsToStatsServer(){
319 | let httpReq = new cyclonRtc.HttpRequestService();
320 | let proxList = this.listManager.getAllProximityLists("list#name")[0];
321 | let neighbors = proxList.getAllElements();
322 | neighbors = neighbors.map((value) => {
323 | return `"${value.key}"`;
324 | });
325 |
326 | // let localEntry = this.listManager.getAllLocalEntries()[0].key;
327 | // httpReq.get(`http://localhost:3500/stats/neighbors_updated?json={"id":"${localEntry}","neighbors":[${neighbors}]}`);
328 | }
329 |
330 |
331 | __listenForPackets(){
332 | let self =this;
333 | this.rtc.onChannel("search", function (data) {
334 | data.receive("unionp2p", 20000).then((message) => {
335 | console.info("data received!");
336 | console.info(message);
337 | self.__handleReceivedPacket(message.data);
338 | data.rtcDataChannel.close();
339 | data.close();
340 | self.__listenForPackets();
341 | },(reason => {
342 | data.rtcDataChannel.close();
343 | data.close();
344 | }));
345 | });
346 | }
347 |
348 | __handleReceivedPacket(packet) {
349 | if (this.handledPacketIds.includes(packet[constants.PACKET_FIELD.PACKET_ID])) {
350 | return;
351 | }
352 | console.log(this.__controllers.length);
353 | console.info(`handling packet ${JSON.stringify(packet)}`);
354 | for (let controller of this.__controllers) {
355 | console.info("testing controller");
356 | // if (controller.handlePacket(packet))
357 | // return;
358 | controller.handlePacket(packet);
359 | }
360 | if (this.handledPacketIds.length>500){
361 | this.handledPacketIds = [];
362 | }
363 | this.handledPacketIds.push(packet[constants.PACKET_FIELD.PACKET_ID]);
364 | // console.error(packet[constants.PACKET_FIELD.PACKET_TYPE]);
365 | // let stats_obj = {event:constants.EVENTS.SEARCH_DISCARDED,id:packet[constants.PACKET_FIELD.PACKET_ID],source_name:this.name};
366 | // this.emit("stats", stats_obj);
367 | // let httpReq = new cyclonRtc.HttpRequestService();
368 | // httpReq.get(`http://localhost:3500/stats/search_discarded?id=${packet[constants.PACKET_FIELD.PACKET_ID]}&node_name=${this.name}`);
369 | }
370 |
371 | /**
372 | *
373 | * @param obj
374 | * @param {uuid} targetNodeId
375 | */
376 | sendObjectToNode(obj, targetNodePointer) {
377 | this.rtc.openChannel("search", targetNodePointer).then((channel) => {
378 | channel.send("unionp2p", {
379 | data: obj
380 | });
381 | setTimeout(()=>{
382 | channel.close();
383 | },5000)
384 | });
385 | }
386 |
387 |
388 |
389 | __getNodePointerForNodeUUID(id) {
390 | for (let pointer of this.proximityList.getAllElements()) {
391 | if (pointer["id"] === id) {
392 | return pointer;
393 | }
394 | }
395 | console.info(id);
396 | return undefined;
397 | }
398 |
399 |
400 | attachController(controller) {
401 | this.__controllers.push(controller);
402 | }
403 |
404 | _removeController(controller){
405 | console.log("removing search request controller");
406 | this.__controllers = this.__controllers.filter((value => {
407 | return value !== controller;
408 | }));
409 | }
410 |
411 | getRandomSamplePointers(){
412 | return Array.from(this.__cyclonNode.getNeighbourSet().getContents().values());
413 | }
414 | getRandomSampleIds(){
415 | return Array.from(this.__cyclonNode.getNeighbourSet().getContents().keys());
416 | // return Object.values(this.__cyclonNode.getNeighbourSet().getContents()).map((value => {
417 | // return value.id;
418 | // }));
419 | }
420 | getId() {
421 | return this.__cyclonNode.getId();
422 | }
423 |
424 | }
425 |
426 | module.exports = {Node};
427 |
428 |
429 |
430 |
--------------------------------------------------------------------------------