Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@browser-network/network

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@browser-network/network - npm Package Compare versions

Comparing version 0.0.7 to 0.0.8

dist/src/Connection.d.ts

37

dist/src/index.d.ts

@@ -5,11 +5,11 @@ /// <reference types="node" />

import { Repeater } from './Repeater';
import EventEmitter from './EventEmitter';
import TypedEventEmitter from './TypedEventEmitter';
import BehaviorCache from './BehaviorCache';
import { NetworkConfig } from './NetworkConfig.d';
import { Connection, PendingConnection } from './Connection.d';
import { Connection } from './Connection';
export declare type Message = Mes.Message;
declare type NetworkProps = {
secret: t.Secret;
networkId: t.NetworkId;
switchAddress: t.SwitchAddress;
networkId: t.NetworkId;
clientId: t.ClientId;
config?: Partial<NetworkConfig>;

@@ -19,5 +19,6 @@ };

'switchboard-response': t.SwitchboardBook;
'add-connection': PendingConnection | Connection;
'add-connection': Connection;
'destroy-connection': Connection['id'];
'broadcast-message': Mes.Message;
'bad-message': Mes.Message;
'message': {

@@ -28,5 +29,5 @@ appId: string;

};
export declare class Network extends EventEmitter<Events> {
export declare class Network extends TypedEventEmitter<Events> {
config: NetworkConfig;
clientId: t.ClientId;
address: t.Address;
networkId: t.NetworkId;

@@ -39,4 +40,5 @@ switchAddress: t.SwitchAddress;

behaviorCache: BehaviorCache;
private _secret;
_connections: {
[connectionId: t.GUID]: PendingConnection | Connection;
[connectionId: t.GUID]: Connection;
};

@@ -46,6 +48,7 @@ _seenMessageIds: {

};
_switchboardVolunteerDelayTimeout: ReturnType<typeof setInterval>;
_switchboardVolunteerDelayTimeout: ReturnType<typeof setTimeout>;
_offerBroadcastInterval: ReturnType<typeof setInterval>;
_garbageCollectInterval: ReturnType<typeof setInterval>;
constructor({ switchAddress, networkId, clientId, config }: NetworkProps);
constructor({ secret, switchAddress, networkId, config }: NetworkProps);
teardown(): void;
broadcast<M extends {

@@ -55,9 +58,10 @@ type: string;

appId: string;
}>(message: M & Partial<Mes.Message>): void;
connections(): (PendingConnection | Connection)[];
}>(message: M & Partial<Mes.Message>): Promise<void>;
connections(): Connection[];
isRude(ip: t.IPAddress): boolean;
addToRudeList(ip: t.IPAddress, clientId?: t.ClientId): void;
addToRudeList(ip: t.IPAddress, address?: t.Address): void;
private startOfferBroadcastInterval;
private stopOfferBroadcastInterval;
private startGarbageCollectionInterval;
private rebroadcast;
private stopGarbageCollectionInterval;
private broadcastMessage;

@@ -83,9 +87,6 @@ private beginSwitchboardRequestPeriod;

private destroyConnection;
private send;
private signal;
private broadcastOffer;
private hasConnection;
private getConnectionByClientId;
private getConnectionByAddress;
private getOrGenerateOpenConnection;
}
export {};

@@ -27,2 +27,25 @@ var __extends = (this && this.__extends) || (function () {

};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -73,3 +96,3 @@ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }

else if (typeof define === "function" && define.amd) {
define(["require", "exports", "simple-peer", "axios", "uuid", "./util", "./Repeater", "./EventEmitter", "./BehaviorCache"], factory);
define(["require", "exports", "axios", "uuid", "./util", "./Repeater", "./TypedEventEmitter", "./BehaviorCache", "./Connection", "@browser-network/crypto"], factory);
}

@@ -80,3 +103,2 @@ })(function (require, exports) {

exports.Network = void 0;
var simple_peer_1 = __importDefault(require("simple-peer"));
var axios_1 = __importDefault(require("axios"));

@@ -88,6 +110,7 @@ // Tried using crypto.randomUUID() but browserify like 10x's the build

var Repeater_1 = require("./Repeater");
var EventEmitter_1 = __importDefault(require("./EventEmitter"));
var TypedEventEmitter_1 = __importDefault(require("./TypedEventEmitter"));
var BehaviorCache_1 = __importDefault(require("./BehaviorCache"));
var Connection_1 = require("./Connection");
var bnc = __importStar(require("@browser-network/crypto"));
var debug = (0, util_1.debugFactory)('Network');
var IS_NODE = typeof process !== 'undefined';
// Every app, even including this network, needs a unique id to identify

@@ -109,7 +132,8 @@ // messages coming over the network. The only constraint is that you

// Same
var SWITCHBOARD_REQUEST_ITERATIONS = 15;
var SWITCHBOARD_REQUEST_ITERATIONS = Infinity;
// TODO Use TypedEventEmitter only as a type and not as the actual code.
var Network = /** @class */ (function (_super) {
__extends(Network, _super);
function Network(_a) {
var switchAddress = _a.switchAddress, networkId = _a.networkId, clientId = _a.clientId, _b = _a.config, config = _b === void 0 ? {} : _b;
var secret = _a.secret, switchAddress = _a.switchAddress, networkId = _a.networkId, _b = _a.config, config = _b === void 0 ? {} : _b;
var _this = _super.call(this) || this;

@@ -119,3 +143,4 @@ _this.rudeIps = {};

_this._seenMessageIds = {};
_this.config = Object.assign(config, {
_this._secret = secret;
_this.config = Object.assign({
offerBroadcastInterval: 1000 * 5,

@@ -127,6 +152,6 @@ switchboardRequestInterval: 1000 * 3,

maxConnections: 10
});
}, config);
_this.switchAddress = switchAddress;
_this.networkId = networkId;
_this.clientId = clientId;
_this.address = bnc.derivePubKey(secret);
_this.startOfferBroadcastInterval();

@@ -151,15 +176,51 @@ _this.startGarbageCollectionInterval();

}
// Stop all listeners, intervals, and connections, so that a process running a network
// can gracefully stop its own process.
Network.prototype.teardown = function () {
this.switchboardRequester.stop();
this.stopOfferBroadcastInterval();
this.stopGarbageCollectionInterval();
clearTimeout(this._switchboardVolunteerDelayTimeout);
for (var _i = 0, _a = this.connections(); _i < _a.length; _i++) {
var c = _a[_i];
this.destroyConnection(c);
}
for (var conId in this._connections) {
delete this._connections[conId];
}
this.off();
};
// The primary means of sending a message into the network for an application.
// You can pass in a union of your different message types for added type safety.
Network.prototype.broadcast = function (message) {
// TODO require: data, appId, type
// We forbid id and clientId from being passed in.
message.id = (0, uuid_1.v4)();
message.clientId = this.clientId;
// TODO validate shape here
var toBroadcast = Object.assign(message, {
ttl: 6,
destination: '*'
return __awaiter(this, void 0, void 0, function () {
var toBroadcast, _a, _b;
var _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
// required: data, appId, type
if (!message.type || !message.data || !message.appId) {
throw new TypeError('Must supply at least type, data and appId');
}
toBroadcast = Object.assign({
id: (0, uuid_1.v4)(),
address: this.address,
ttl: 6,
destination: '*',
signatures: []
}, message);
_b = (_a = toBroadcast.signatures).push;
_c = {
signer: this.address
};
return [4 /*yield*/, bnc.sign(this._secret, toBroadcast)];
case 1:
_b.apply(_a, [(_c.signature = _d.sent(),
_c)]);
this.broadcastMessage(toBroadcast);
return [2 /*return*/];
}
});
});
this.broadcastMessage(toBroadcast);
};

@@ -176,10 +237,10 @@ // List of all our current connections

// Add an ip to a rude list, which means we won't connect to then any more.
// If an optional clientId is provided, and we're connected to that clientId,
// If an optional address is provided, and we're connected to that address,
// we'll drop them as well.
Network.prototype.addToRudeList = function (ip, clientId) {
Network.prototype.addToRudeList = function (ip, address) {
this.rudeIps[ip] = Date.now();
debug(1, 'added to rude list:', ip, clientId);
debug(1, 'added to rude list:', ip, address);
// We can check and make sure we're aren't / don't stay connected to this person
if (clientId) {
var connection = this.getConnectionByClientId(clientId);
if (address) {
var connection = this.getConnectionByAddress(address);
if (!connection) {

@@ -192,3 +253,3 @@ return;

data: 'rude',
destination: clientId
destination: address
});

@@ -200,12 +261,16 @@ this.destroyConnection(connection);

Network.prototype.startOfferBroadcastInterval = function () {
var _this = this;
if (this._offerBroadcastInterval) {
return;
}
this._offerBroadcastInterval = setInterval(this.broadcastOffer.bind(this), this.config.offerBroadcastInterval);
this._offerBroadcastInterval = setInterval(function () {
var openCon = _this.getOrGenerateOpenConnection();
if (openCon.negotiation.sdp)
_this.broadcastOffer();
}, this.config.offerBroadcastInterval);
};
// // Temporarily removed but kept for safe keeping
// private stopOfferBroadcastInterval() {
// clearInterval(this._offerBroadcastInterval)
// delete this._offerBroadcastInterval
// }
Network.prototype.stopOfferBroadcastInterval = function () {
clearInterval(this._offerBroadcastInterval);
delete this._offerBroadcastInterval;
};
// Safely start it

@@ -218,17 +283,9 @@ Network.prototype.startGarbageCollectionInterval = function () {

};
// // Temporarily removed but kept for safe keeping
// private stopGarbageCollectionInterval() {
// clearInterval(this._garbageCollectInterval)
// delete this._garbageCollectInterval
// }
Network.prototype.rebroadcast = function (message) {
if (!message.ttl) {
return;
}
message.ttl -= 1;
this.broadcastMessage(message);
Network.prototype.stopGarbageCollectionInterval = function () {
clearInterval(this._garbageCollectInterval);
delete this._garbageCollectInterval;
};
// Send message to all our connections
// TODO Fold this into broadcast
Network.prototype.broadcastMessage = function (message) {
// TODO validate message shape at runtime
// TODO make helpers for this

@@ -240,5 +297,25 @@ this._seenMessageIds[message.id] = Date.now();

// anything over that.
if (!connection.negotiation.sdp)
return;
this.send(connection, message);
if (!connection.negotiation.sdp) {
continue;
}
try {
// The difference between write and send is that write queues, send
// throws if it's not writable yet. Previously there was a race
// condition here leading to many initial connections when
// using write. Once we removed the asynchronicity from connection
// creation, that race condition went away and we're free to use .write
// again. However, ephemerality is built into the network, so it's understood
// that messages won't always make it. With our rudeness checking on, maybe
// it's best not to queue up messages before sending, and just send when
// we're connected.
// connection.peer.write(JSON.stringify(message))
if (!connection.peer.connected) {
continue;
}
connection.peer.send(JSON.stringify(message));
debug(5, 'sending', message, 'to', connection.address);
}
catch (e) {
debug(3, 'got error trying to send to', connection.address, e);
}
}

@@ -256,4 +333,3 @@ this.emit('broadcast-message', message);

this.switchboardRequester.begin();
// TODO this is _alright_ but not great.
this.broadcastMessage({
this.broadcast({
id: (0, uuid_1.v4)(),

@@ -264,3 +340,3 @@ appId: APP_ID,

ttl: 2,
clientId: this.clientId,
address: this.address,
data: {}

@@ -277,6 +353,6 @@ });

existingConnection = this.getOrGenerateOpenConnection();
// We don't want to send switchboard requests for PendingConnections
// We don't want to send switchboard requests for pending connections
if (!existingConnection.negotiation.sdp)
return [2 /*return*/];
return [4 /*yield*/, this.sendNegotiationToSwitchingService(__assign({ clientId: this.clientId, networkId: this.networkId, connectionId: existingConnection.id }, existingConnection.negotiation))];
return [4 /*yield*/, this.sendNegotiationToSwitchingService(__assign({ address: this.address, networkId: this.networkId, connectionId: existingConnection.id }, existingConnection.negotiation))];
case 1:

@@ -301,3 +377,3 @@ resp = _a.sent();

switch (negotiation.type) {
case 'offer': {
case 'offer':
var connection_1 = this_1.handleOffer(negotiation);

@@ -307,19 +383,9 @@ if (!connection_1) {

}
// TODO Factor this right on out of here. Connection should be a class
// that emits an event when the sdp info is ready, among other things.
var interval_1 = setInterval(function () {
// We are just gonna keep trying until the sdp has come through, which comes a little
// bit after the connection is initially formed.
if (!connection_1.negotiation.sdp)
return;
connection_1.on('sdp', function () {
_this.sendNegotiationToSwitchingService(__assign({ connectionId: connection_1.id, timestamp: Date.now(), networkId: _this.networkId }, connection_1.negotiation));
// we only want to do this once.
clearInterval(interval_1);
}, 300);
});
break;
}
case 'answer': {
case 'answer':
this_1.handleAnswer(negotiation);
break;
}
default:

@@ -342,3 +408,4 @@ (0, util_1.exhaustive)(negotiation, 'We got something from the switchboard that has a weird type');

};
// TODO Pull this off the proto
// TODO Pull this off the proto, along with the other switchboard stuff. These
// should be able to live entirely on their own class.
Network.prototype.sendNegotiationToSwitchingService = function (negotiation) {

@@ -365,42 +432,75 @@ return __awaiter(this, void 0, void 0, function () {

Network.prototype.handleMessage = function (message) {
// If we've already seen this message, we do nothing
// with it.
if (this._seenMessageIds[message.id]) {
return;
}
// Now we've seen this message.
this._seenMessageIds[message.id] = Date.now();
debug(5, 'handleMessage:', message);
// We are only interested in our own application here.
// The network is actually an application on the network, lolz.
// Note we're using 'massage' here only so typescript knows
// about the correct typing. Try getting exhaustiveness without
// it.
var massage = message;
if (message.appId === APP_ID) {
switch (massage.type) {
case 'offer':
this.handleOfferMessage(massage);
break;
;
case 'answer':
this.handleAnswerMessage(massage);
break;
;
case 'log':
this.handleLogMessage(massage);
break;
;
case 'switchboard-volunteer':
this.handleSwitchboardVolunteerMessage(massage);
break;
;
default:
(0, util_1.exhaustive)(massage, 'Someone sent a message with our appId but of the wrong type!');
break;
;
}
}
this.rebroadcast(message);
this.emit('message', { appId: message.appId, message: message });
return __awaiter(this, void 0, void 0, function () {
var signatures, signature, isValidSignature, massage;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// If we've already seen this message, we do nothing
// with it.
if (this._seenMessageIds[message.id]) {
return [2 /*return*/];
}
// Now we've seen this message.
this._seenMessageIds[message.id] = Date.now();
debug(5, 'handleMessage:', message);
// Ensure the message is cryptographically sound
// Firstly, if there are no signatures, it is not sound.
if (message.signatures.length === 0) {
debug(3, 'received message with no signatures!', message);
this.emit('bad-message', message);
}
signatures = [];
_a.label = 1;
case 1:
if (!(message.signatures.length !== 0)) return [3 /*break*/, 3];
signature = message.signatures.pop();
signatures.unshift(signature);
return [4 /*yield*/, bnc.verifySignature(message, signature.signature, signature.signer)];
case 2:
isValidSignature = _a.sent();
if (!isValidSignature) {
debug(3, 'received message with unverifiable signature!', message);
this.emit('bad-message', message);
return [2 /*return*/];
}
return [3 /*break*/, 1];
case 3:
// Now we repair the mutation from above
message.signatures = signatures;
massage = message;
if (message.appId === APP_ID) {
switch (massage.type) {
case 'offer':
this.handleOfferMessage(massage);
break;
;
case 'answer':
this.handleAnswerMessage(massage);
break;
;
case 'log':
this.handleLogMessage(massage);
break;
;
case 'switchboard-volunteer':
this.handleSwitchboardVolunteerMessage(massage);
break;
;
default:
(0, util_1.exhaustive)(massage, 'Someone sent a message with our appId but of the wrong type!');
break;
;
}
}
// Instead of decrementing the ttl value, since the signatures depend on it
// staying the same, we count the signatures to see how many hops the message
// has taken.
if (message.signatures.length < message.ttl) {
this.broadcast(message);
}
this.emit('message', { appId: message.appId, message: message });
return [2 /*return*/];
}
});
});
};

@@ -413,10 +513,7 @@ Network.prototype.handleOfferMessage = function (message) {

}
var interval = setInterval(function () {
if (!connection.negotiation.sdp)
return;
_this.broadcastMessage(__assign(__assign({}, connection.negotiation), { appId: APP_ID, id: (0, uuid_1.v4)(), ttl: 6, clientId: _this.clientId, destination: message.clientId, data: {
connection.on('sdp', function () {
_this.broadcast(__assign(__assign({}, connection.negotiation), { appId: APP_ID, id: (0, uuid_1.v4)(), ttl: 6, address: _this.address, destination: message.address, data: {
connectionId: message.data.connectionId
} }));
clearInterval(interval);
}, 300);
});
};

@@ -428,13 +525,13 @@ Network.prototype.handleAnswerMessage = function (message) {

// Only log messages sent to us
if (!['*', this.clientId].includes(message.destination)) {
if (!['*', this.address].includes(message.destination)) {
return;
}
console.log(message.clientId + ':', message.data.contents);
console.log(message.address + ':', message.data.contents);
};
Network.prototype.handleSwitchboardVolunteerMessage = function (message) {
if (!this.config.respectSwitchboardVolunteerMessages) {
debug(5, 'Switchboard Volunteer Message heard but feature is disabled. Heard from:', message.clientId);
debug(5, 'Switchboard Volunteer Message heard but feature is disabled. Heard from:', message.address);
return;
}
debug(3, 'heard switchboard volunteer, backing off switchboard requests:', message.clientId);
debug(3, 'heard switchboard volunteer, backing off switchboard requests:', message.address);
this.switchboardRequester.stop();

@@ -456,3 +553,3 @@ if (this._switchboardVolunteerDelayTimeout) {

// Somebody else already got to this open connection
connection.clientId ||
connection.address ||
// The ip trying to connect is on our naughty list or not presenting an sdp string

@@ -466,5 +563,5 @@ !answer.sdp || this.isRude((0, util_1.getIpFromRTCSDP)(answer.sdp)) ||

// Now we know who is at the other end of the open offer we'd previously created.
connection.clientId = answer.clientId;
connection.address = answer.address;
// Punch through that nat
this.signal(connection.peer, answer);
connection.signal(answer);
};

@@ -477,5 +574,5 @@ Network.prototype.handleOffer = function (offer) {

// It's ourselves
offer.clientId === this.clientId ||
offer.address === this.address ||
// We're are already connected to this client
this.hasConnection(offer.clientId) ||
!!this.getConnectionByAddress(offer.address) ||
// They're on our rude list or not presenting an sdp string

@@ -488,15 +585,14 @@ !offer.sdp || this.isRude((0, util_1.getIpFromRTCSDP)(offer.sdp)) ||

// There's an offer in the book for a client to whom we're not connected.
debug(3, 'fielding an offer from', offer.clientId);
debug(3, 'fielding an offer from', offer.address);
// Generate the answer response to peer's answer (new peer object)
// Always will be present b/c it's new
var connection = this.generateAnswerConnection(offer);
this.addConnection(connection, offer.clientId);
this.addConnection(connection, offer.address);
return connection;
};
Network.prototype.generateOfferConnection = function () {
var peer = new simple_peer_1["default"]({ initiator: true, trickle: false, wrtc: IS_NODE ? require('wrtc') : undefined });
var id = (0, uuid_1.v4)();
var negotiation = {
type: 'offer',
clientId: this.clientId,
address: this.address,
connectionId: id,

@@ -507,16 +603,9 @@ sdp: null,

};
var pendingConnection = { id: id, peer: peer, negotiation: negotiation };
peer.on('signal', function (data) {
if (data.type === 'offer') {
pendingConnection.negotiation.sdp = data.sdp;
}
});
return pendingConnection;
return new Connection_1.Connection(id, true, negotiation);
};
Network.prototype.generateAnswerConnection = function (offer) {
debug(5, 'generateAnswerConnection called for offer:', offer.clientId, offer.connectionId);
var peer = new simple_peer_1["default"]({ initiator: false, trickle: false, wrtc: IS_NODE ? require('wrtc') : undefined });
debug(5, 'generateAnswerConnection called for offer:', offer.address, offer.connectionId);
var negotiation = {
type: 'answer',
clientId: this.clientId,
address: this.address,
connectionId: offer.connectionId,

@@ -527,15 +616,11 @@ sdp: null,

};
var pendingConnection = { id: (0, uuid_1.v4)(), peer: peer, negotiation: negotiation };
peer.on('signal', function (data) {
if (data.type === 'answer') {
pendingConnection.negotiation.sdp = data.sdp;
}
});
this.signal(peer, offer);
return pendingConnection;
var connection = new Connection_1.Connection((0, uuid_1.v4)(), false, negotiation);
connection.signal(offer);
return connection;
};
Network.prototype.addConnection = function (connection, clientId) {
Network.prototype.addConnection = function (connection, address) {
// This always needs to happen when we add the connection to our pool,
// lest we're adding an offer.
connection.clientId = clientId;
if (address)
connection.registerAddress(address);
this._connections[connection.id] = connection;

@@ -549,13 +634,13 @@ this.registerRTCEventHandlers(connection);

peer.on('connect', function () {
debug(2, 'CONNECT', connection.clientId);
debug(2, 'CONNECT', connection.address);
// Send a welcome log message for the warm fuzzies
_this.broadcastMessage({
_this.broadcast({
type: 'log',
clientId: _this.clientId,
address: _this.address,
appId: APP_ID,
id: (0, uuid_1.v4)(),
ttl: 1,
destination: connection.clientId,
destination: connection.address,
data: {
contents: 'you are now proudly connected to ' + _this.clientId
contents: 'Heyo!'
}

@@ -565,9 +650,9 @@ });

peer.on('data', function (data) {
var clientId = connection.clientId, negotiation = connection.negotiation;
var address = connection.address, negotiation = connection.negotiation;
var peerAddress = (0, util_1.getIpFromRTCSDP)(negotiation.sdp);
debug(5, 'got message from:', peerAddress, clientId);
debug(5, 'got message from:', peerAddress, address);
// Ensure the machine on the other end of this connection is behaving themselves
if (!_this.behaviorCache.isOnGoodBehavior(peerAddress)) {
debug(1, 'whoops, the machine belonging to', clientId, 'is exhibiting bad behavior!');
_this.addToRudeList(peerAddress, clientId);
debug(1, 'whoops, the machine belonging to', address, 'is exhibiting bad behavior!');
_this.addToRudeList(peerAddress, address);
return;

@@ -581,3 +666,3 @@ }

catch (e) {
return debug(3, 'failed to parse message from', clientId + ':', str, e);
return debug(3, 'failed to parse message from', address + ':', str, e);
}

@@ -587,5 +672,5 @@ _this.handleMessage(message);

peer.on('close', function () { _this.destroyConnection(connection); });
peer.on('end', function () { debug(5, 'p.on("end") fired for client', connection.clientId); });
peer.on('writable', function () { debug(5, 'p.on("writable") fired for client', connection.clientId); });
peer.on('error', function (err) { debug(4, "p.on(error) handler for ".concat(connection.clientId, ":"), err); });
peer.on('end', function () { debug(5, 'p.on("end") fired for client', connection.address); });
peer.on('writable', function () { debug(5, 'p.on("writable") fired for client', connection.address); });
peer.on('error', function (err) { debug(4, "p.on(error) handler for ".concat(connection.address, ":"), err); });
};

@@ -609,3 +694,3 @@ Network.prototype.garbageCollect = function () {

// But it'd be better if we weren't having duplicate clients at all.
var seenClientIds = {};
var seenAddresses = {};
// The actual garbage collection action

@@ -618,3 +703,3 @@ var collect = function (connection) {

var connection = this._connections[connectionId];
var clientId = connection.clientId, destroyed = connection.peer.destroyed;
var address = connection.address, destroyed = connection.peer.destroyed;
if (destroyed) {

@@ -634,10 +719,12 @@ return collect(connection);

// but that did not seem to have any effect.
// This reads "if we've seen this clientId already, assess if either of the connections
// This reads "if we've seen this address already, assess if either of the connections
// have no channelName and remove it if it doesn't."
var seenConnectionId = seenClientIds[clientId];
var seenConnectionId = seenAddresses[address];
if (seenConnectionId) {
// These two mean if either has no channelName, remove it.
// @ts-ignore -- not in the types, but not underscore prefixed..
if (connection.peer.channelName === null) {
return collect(connection);
}
// @ts-ignore
if (this._connections[seenConnectionId].peer.channelName === null) {

@@ -647,7 +734,7 @@ return collect(this._connections[seenConnectionId]);

}
seenClientIds[clientId] = connection.id;
seenAddresses[address] = connection.id;
}
};
Network.prototype.destroyConnection = function (connection) {
debug(4, 'destroying connection', connection.clientId);
debug(4, 'destroying connection', connection.address);
var peer = connection.peer;

@@ -660,66 +747,23 @@ peer.removeAllListeners();

};
// Send message to a specific connection
Network.prototype.send = function (connection, message) {
try {
// The difference between write and send is that write queues, send
// throws if it's not writable yet. Previously there was a race
// condition here leading to many initial connections when
// using write. Once we removed the asynchronicity from connection
// creation, that race condition went away and we're free to use .write
// again. However, ephemerality is built into the network, so it's understood
// that messages won't always make it. With our rudeness checking on, maybe
// it's best not to queue up messages before sending, and just send when
// we're connected.
// connection.peer.write(JSON.stringify(message))
if (!connection.peer.connected) {
return;
}
connection.peer.send(JSON.stringify(message));
debug(5, 'sending', message, 'to', connection.clientId);
}
catch (e) {
debug(3, 'got error trying to send to', connection.clientId, e);
}
};
// Safely signal a peer
Network.prototype.signal = function (peer, data) {
debug(5, 'signaling peer:', peer, data);
try {
peer.signal(data);
}
catch (e) {
debug(3, 'error signaling peer:', e);
}
};
Network.prototype.broadcastOffer = function () {
return __awaiter(this, void 0, void 0, function () {
var openConnection, offer;
return __generator(this, function (_a) {
openConnection = this.getOrGenerateOpenConnection();
offer = {
id: (0, uuid_1.v4)(),
ttl: 6,
type: 'offer',
clientId: this.clientId,
appId: APP_ID,
destination: '*',
data: __assign({ timestamp: Date.now(), connectionId: openConnection.id }, openConnection.negotiation)
};
this.broadcastMessage(offer);
return [2 /*return*/];
});
});
var openConnection = this.getOrGenerateOpenConnection();
// We don't want to send messages about pending connections
if (!openConnection.negotiation.sdp)
return;
var offer = {
ttl: 6,
type: 'offer',
appId: APP_ID,
destination: '*',
data: __assign({ timestamp: Date.now(), connectionId: openConnection.id }, openConnection.negotiation)
};
this.broadcast(offer);
};
Network.prototype.hasConnection = function (clientId) {
return !!this.getConnectionByClientId(clientId);
Network.prototype.getConnectionByAddress = function (address) {
return this.connections().find(function (con) { return con.address === address; });
};
Network.prototype.getConnectionByClientId = function (clientId) {
return this.connections().find(function (con) { return con.clientId === clientId; });
};
// If we have an open connection in the pool, return that.
// Otherwise, generate an open connection.
Network.prototype.getOrGenerateOpenConnection = function () {
// return this.connections().find(con => con.negotiation.type === 'offer') // TODO
// let oc = this.connections().find(con => !con.peer.connected) // TODO
var oc = this.connections().find(function (con) { return !con.clientId; }); // TODO
var oc = this.connections().find(function (con) { return !con.address; });
if (!oc) {

@@ -732,4 +776,4 @@ oc = this.generateOfferConnection();

return Network;
}(EventEmitter_1["default"]));
}(TypedEventEmitter_1["default"]));
exports.Network = Network;
});

@@ -24,2 +24,3 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {

return function (logLevel) {
var _a;
var args = [];

@@ -29,3 +30,3 @@ for (var _i = 1; _i < arguments.length; _i++) {

}
if (globalThis.DEBUG >= logLevel) {
if (globalThis.DEBUG >= logLevel || Number((_a = globalThis.process) === null || _a === void 0 ? void 0 : _a.env.DEBUG) >= logLevel) {
var d = new Date();

@@ -32,0 +33,0 @@ console.log.apply(console, __spreadArray(["[".concat(logLevel, "] ").concat(appName, " ").concat(d.toLocaleTimeString(), ": ")], args, false));

{
"name": "@browser-network/network",
"version": "0.0.7",
"version": "0.0.8",
"description": "A WebRTC based direct peer to peer network in the browser.",

@@ -12,3 +12,3 @@ "main": "./dist/src/index.js",

"scripts": {
"test": "ts-node test/index.ts",
"test": "tap --ts --no-timeout --no-coverage test/*.ts",
"clean": "shx rm -rf dist build umd; shx mkdir umd",

@@ -19,3 +19,3 @@ "compile:ts": "tsc",

"build": "npm run clean; npm-run-all compile:**",
"build:watch": "nodemon -e ts,json -i dist -x 'npm run compile:ts && npm run compile:pack'",
"build:watch": "nodemon -e ts,json -i dist -i build -i umd -x 'npm run compile:ts && npm run compile:pack'",
"start:dev": "node serve.js & npm run build:watch",

@@ -46,3 +46,5 @@ "release": "npm run build && np --no-cleanup --no-tests --no-yarn --message=\"New release! Version: %s\""

"dependencies": {
"@browser-network/crypto": "^0.0.1",
"axios": "^0.26.1",
"eccrypto": "^1.1.6",
"simple-peer": "^9.11.1",

@@ -54,5 +56,6 @@ "uuid": "^8.3.2",

"@mapbox/node-pre-gyp": "^1.0.9",
"@types/eccrypto": "^1.1.3",
"@types/node": "^16",
"@types/simple-peer": "^9.11.4",
"@types/tape": "^4.13.2",
"@types/tap": "^15.0.6",
"@types/uuid": "^8.3.4",

@@ -64,4 +67,5 @@ "browserify": "^17.0.0",

"shx": "^0.3.4",
"tap": "^16.0.1",
"tap-spec": "^5.0.0",
"tape": "^5.5.2",
"ts-node": "^10.7.0",
"typescript": "^4.4.4",

@@ -68,0 +72,0 @@ "uglify-js": "^3.15.3"

@@ -66,4 +66,8 @@ # Distributed Browser Network

browser window :P Note that if you wish to do _slightly more programming_, you can
also run a node.js node with the same `networkId`.
also run a node.js node with the same `networkId`, and it will act as a headless
browser window, fulfilling all the same functionality as a browser window would.
* Cryptographic security - Network uses `eccrypto` to ensure veracity of messages.
It's cryptographically difficult to spoof or modify a message that's not your own.
### How it works

@@ -148,3 +152,3 @@

switchAddress: 'http://localhost:5678', // default address of switchboard
clientId: crypto.randomUUID(), // arbitrary string
address: crypto.randomUUID(), // arbitrary string
networkId: 'test-network'

@@ -197,3 +201,3 @@ })

switchAddress: 'http://localhost:5678', // default address of switchboard
clientId: globalThis.crypto.randomUUID(), // arbitrary string
address: globalThis.crypto.randomUUID(), // arbitrary string
networkId: '<something unique but the same b/t all your nodes>',

@@ -287,14 +291,5 @@ config:{

* Assess how much of our inter peer data could be represented with buffers
* if a broadcast is made with a specific clientId, and we're connected to that clientId,
just go ahead and send directly to that clientId instead of broadcasting to everyone.
* if a broadcast is made with a specific address, and we're connected to that address,
just go ahead and send directly to that address instead of broadcasting to everyone.
* Log message config param - toggle for whether to respect log messages. Might be
a security vulnerability.
* Bring cryptographic integrity into this project. Originally I was unsure of whether
it would be helpful because it doesn't prevent spam. If you got blocked somehow,
you could just change your priv key and boom you're a new user. Although now that
I write it out, I could see a reputation system working just fine about that.
Anyways there's another reason to bring it in - to prevent spoofing the clientId
of a message. Right now I can broadcast a message and put any clientId I'd like
on it. As far as network is currently concerned this is not an issue, you can't spoof
someone's sdp information. The worst you could do is confuse someone's connection pool.
Actually that's a low hanging DOS right there. So yeah, network needs cryptographic principles.

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc