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.3.3 to 0.4.0

30

dist/src/Connection.d.ts

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

suppliedOfferNegotiation?: t.OfferNegotiation;
/**
* @description If supplied, the SDP info will be encrypted with EC public key
* encryption so that only the foreign address can read it. This is important
* because the SDP info contains sensitive IP address related information and is
* passed all around the network, and is publicly available on the
* switchboard. This should always be supplied when possible, namely when the
* network is in encrypted mode.
*/
secret?: t.Secret;
};

@@ -38,5 +47,3 @@ export declare class Connection extends EventEmitter {

* @description The public key crypto address of the node on the other side of this
* connection. If there is no address on the connection, that means it's an
* "open connection", one the node is keeping around and broadcasting
* connection information from in RTC "offer" form.
* connection.
*/

@@ -80,2 +87,3 @@ address: t.Address;

answer?: t.AnswerNegotiation | t.PendingAnswerNegotiation;
private _secret;
/**

@@ -105,8 +113,20 @@ * @description A Connection represents the linking between two network nodes.

get state(): STATE;
_handleAnswerNegotiation(answer: t.AnswerNegotiation): Promise<void>;
private get _isPending();
_handleAnswerNegotiation(answer: t.AnswerNegotiation): void;
/**
* @description If the network is running in encrypted mode, we're encrypting our SDP. So this
* encrypts it if we're in encrypted mode.
*/
private _conditionallyEncryptSdp;
/**
* @description Similar to above.
*/
private _conditionallyDecryptSdp;
}
/**
* @description A helper mainly for creating multiple connections simultaneously and waiting for them
* to move out of their pending state
* to move out of their pending state in convenient promise form
*
* TODO Can we just have a method on connection itself called untilReady() that resolves a promise when
* it's in a ready state?
*/

@@ -113,0 +133,0 @@ export declare abstract class ConnectionFactory {

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

})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
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) {

@@ -62,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", "uuid", "simple-peer", "events"], factory);
define(["require", "exports", "uuid", "simple-peer", "events", "@browser-network/crypto"], factory);
}

@@ -72,2 +106,3 @@ })(function (require, exports) {

var events_1 = __importDefault(require("events"));
var bnc = __importStar(require("@browser-network/crypto"));
var IS_NODE = typeof process !== 'undefined';

@@ -91,5 +126,6 @@ var Connection = /** @class */ (function (_super) {

var _this = _super.call(this) || this;
var networkId = props.networkId, selfAddress = props.selfAddress, foreignAddress = props.foreignAddress, suppliedOfferNegotiation = props.suppliedOfferNegotiation;
var networkId = props.networkId, selfAddress = props.selfAddress, foreignAddress = props.foreignAddress, suppliedOfferNegotiation = props.suppliedOfferNegotiation, secret = props.secret;
_this.id = (0, uuid_1.v4)();
_this.address = foreignAddress;
_this._secret = secret;
// bringing in wrtc here costs us 2kb in the build size. 0.9kb in the minified version.

@@ -101,12 +137,26 @@ _this.peer = new simple_peer_1.default({

});
_this.peer.on('signal', function (data) {
if (data.type === 'offer') {
_this.offer.sdp = data.sdp;
_this.emit('state-change');
}
else if (data.type === 'answer') {
_this.answer.sdp = data.sdp;
_this.emit('state-change');
}
});
_this.peer.on('signal', function (data) { return __awaiter(_this, void 0, void 0, function () {
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!(data.type === 'offer')) return [3 /*break*/, 2];
_a = this.offer;
return [4 /*yield*/, this._conditionallyEncryptSdp(data.sdp)];
case 1:
_a.sdp = _c.sent();
this.emit('state-change');
return [3 /*break*/, 4];
case 2:
if (!(data.type === 'answer')) return [3 /*break*/, 4];
_b = this.answer;
return [4 /*yield*/, this._conditionallyEncryptSdp(data.sdp)];
case 3:
_b.sdp = _c.sent();
this.emit('state-change');
_c.label = 4;
case 4: return [2 /*return*/];
}
});
}); });
_this.peer.on('data', function (data) {

@@ -133,3 +183,7 @@ var str = data.toString();

};
_this.peer.signal(suppliedOfferNegotiation);
_this._conditionallyDecryptSdp(suppliedOfferNegotiation.sdp).then(function (sdp) {
var processed = __assign({}, suppliedOfferNegotiation);
processed.sdp = sdp;
_this.peer.signal(processed);
});
}

@@ -170,2 +224,25 @@ else { // We're fixing to be an open connection until another node answers us

});
Connection.prototype._handleAnswerNegotiation = function (answer) {
return __awaiter(this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
// Store our answer in encrypted form
this.answer = answer;
// If we're in encrypted mode, unencrypt the sdp, otherwise just return it
_a = answer;
return [4 /*yield*/, this._conditionallyDecryptSdp(answer.sdp)
// Punch through that nat
];
case 1:
// If we're in encrypted mode, unencrypt the sdp, otherwise just return it
_a.sdp = _b.sent();
// Punch through that nat
this.peer.signal(answer);
return [2 /*return*/];
}
});
});
};
Object.defineProperty(Connection.prototype, "_isPending", {

@@ -183,7 +260,27 @@ get: function () {

});
Connection.prototype._handleAnswerNegotiation = function (answer) {
// Punch through that nat
this.answer = answer;
this.peer.signal(answer);
/**
* @description If the network is running in encrypted mode, we're encrypting our SDP. So this
* encrypts it if we're in encrypted mode.
*/
Connection.prototype._conditionallyEncryptSdp = function (sdp) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (!this._secret)
return [2 /*return*/, sdp];
return [2 /*return*/, bnc.encrypt(sdp, this.address)];
});
});
};
/**
* @description Similar to above.
*/
Connection.prototype._conditionallyDecryptSdp = function (sdp) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (!this._secret)
return [2 /*return*/, sdp];
return [2 /*return*/, bnc.decrypt(sdp, this._secret)];
});
});
};
return Connection;

@@ -194,3 +291,6 @@ }(events_1.default));

* @description A helper mainly for creating multiple connections simultaneously and waiting for them
* to move out of their pending state
* to move out of their pending state in convenient promise form
*
* TODO Can we just have a method on connection itself called untilReady() that resolves a promise when
* it's in a ready state?
*/

@@ -197,0 +297,0 @@ var ConnectionFactory = /** @class */ (function () {

8

dist/src/index.d.ts

@@ -28,3 +28,3 @@ import * as t from './types.d';

};
declare type SecureNetworkProps = CommonNetworkProps & {
export declare type SecureNetworkProps = CommonNetworkProps & {
/**

@@ -37,3 +37,3 @@ * The EC private key that identifies this node on the network. From this,

};
declare type InsecureNetworkProps = CommonNetworkProps & {
export declare type InsecureNetworkProps = CommonNetworkProps & {
/**

@@ -45,3 +45,3 @@ * @description An arbitrary string used as an identifying address. If this

};
declare type NetworkProps = InsecureNetworkProps | SecureNetworkProps;
export declare type NetworkProps = InsecureNetworkProps | SecureNetworkProps;
declare type MinimumMessage = Partial<Message> & {

@@ -86,3 +86,3 @@ type: Message['type'];

on(type: 'add-connection', handler: (connection: Connection) => void): void;
on(type: 'destroy-connection', handler: (id: Connection['id']) => void): void;
on(type: 'destroy-connection', handler: (connection: Connection) => void): void;
on(type: 'switchboard-response', handler: (book: t.SwitchboardResponse) => void): void;

@@ -89,0 +89,0 @@ on(type: 'connection-error', handler: ({ description: string, error: Error }: {

@@ -133,4 +133,4 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {

presenceBroadcastInterval: 1000 * 5,
fastSwitchboardRequestInterval: 500,
slowSwitchboardRequestInterval: 1000 * 3,
fastSwitchboardRequestInterval: 1000 * 1,
slowSwitchboardRequestInterval: 1000 * 5,
garbageCollectInterval: 1000 * 5,

@@ -272,2 +272,3 @@ maxMessageRateBeforeRude: Infinity,

this._messageMemory.add(toBroadcast.id);
// TODO move this to Connection
for (_i = 0, _c = this.activeConnections; _i < _c.length; _i++) {

@@ -366,3 +367,4 @@ connection = _c[_i];

foreignAddress: item.from,
suppliedOfferNegotiation: item.negotiation
suppliedOfferNegotiation: item.negotiation,
secret: _this._secret
});

@@ -376,3 +378,3 @@ }

if ((con === null || con === void 0 ? void 0 : con.state) === 'open') {
_this._emit('connection-process', "Signaling initiator connection to ".concat(item.from, ", connectionId: ").concat(con.id));
_this._emit('connection-process', "switchboard process: Signaling initiator connection to ".concat(item.from, ", connectionId: ").concat(con.id));
con._handleAnswerNegotiation(item.negotiation);

@@ -390,7 +392,9 @@ }

_this._emit('connection-process', "switchboard process: creating new initiator connection to ".concat(address));
return Connection_1.ConnectionFactory.new({
var opts = {
networkId: _this.networkId,
selfAddress: _this.address,
foreignAddress: address
});
foreignAddress: address,
secret: _this._secret
};
return Connection_1.ConnectionFactory.new(opts);
}).filter(Boolean);

@@ -439,2 +443,6 @@ return [4 /*yield*/, Promise.all(newAnswerConnections)];

this._presenceBroadcastInterval = setInterval(function () {
// We don't need to even broadcast our presence if we've hit our max connections
if (_this.activeConnections.length >= _this.config.maxConnections) {
return;
}
_this._broadcastInternal({

@@ -476,6 +484,2 @@ type: 'presence',

this._messageMemory.add(message.id);
// Only handle messages meant for either us or everybody
if (!['*', this.address].includes(message.destination)) {
return [2 /*return*/];
}
if (!this._secret) return [3 /*break*/, 4];

@@ -505,2 +509,13 @@ // Firstly, if there are no signatures, it is not sound.

case 4:
// 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._broadcastInternal(message);
}
this._emit('message', message);
// From here on out, we're only concerned with messages meant for either us or everybody
if (!['*', this.address].includes(message.destination)) {
return [2 /*return*/];
}
massage = message;

@@ -527,9 +542,2 @@ if (message.appId === APP_ID) {

}
// 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._broadcastInternal(message);
}
this._emit('message', message);
return [2 /*return*/];

@@ -573,3 +581,4 @@ }

selfAddress: this.address,
foreignAddress: message.address
foreignAddress: message.data.address,
secret: this._secret
})];

@@ -579,4 +588,9 @@ case 1:

this.registerConnection(connection);
this._emit('connection-process', "broadcasting offer message to ".concat(message.address, ", connectionId: ").concat(connection.id.slice(0, 5), "..."));
this._broadcastInternal({ appId: APP_ID, type: 'offer', data: connection.offer });
this._emit('connection-process', "broadcasting offer message to ".concat(message.data.address, ", connectionId: ").concat(connection.id.slice(0, 5), "..."));
this._broadcastInternal({
type: 'offer',
appId: APP_ID,
destination: message.data.address,
data: connection.offer
});
return [2 /*return*/];

@@ -600,2 +614,6 @@ }

}
// And we don't need to field an offer if we're full up
if (this.activeConnections.length >= this.config.maxConnections) {
return [2 /*return*/];
}
this._emit('connection-process', "received offer message from ".concat(message.address));

@@ -616,4 +634,5 @@ inactiveConnections = this.connections.filter(function (con) {

selfAddress: this.address,
foreignAddress: message.address,
suppliedOfferNegotiation: message.data
foreignAddress: message.data.address,
suppliedOfferNegotiation: message.data,
secret: this._secret
})];

@@ -624,3 +643,8 @@ case 1:

this._emit('connection-process', "broadcasting answer message to ".concat(message.address, ", connectionId: ").concat(connection.id.slice(0, 5), "..."));
this._broadcastInternal({ appId: APP_ID, type: 'answer', data: connection.answer });
this._broadcastInternal({
destination: message.data.address,
appId: APP_ID,
type: 'answer',
data: connection.answer
});
return [2 /*return*/];

@@ -638,2 +662,6 @@ }

}
// We may have broadcasted an offer and in the interim filled up on connections
if (this.activeConnections.length >= this.config.maxConnections) {
return;
}
var connection = this.connections.find(function (con) {

@@ -661,3 +689,2 @@ return con.address === message.address && // for us

connection.peer.on('connect', function () {
_this._emit('add-connection', connection);
// Let's take this opportunity to remove any other connections with the

@@ -671,2 +698,9 @@ // same address that aren't connected. Keep the place clean. It's possible

});
// Sometimes because of the racy nature of connecting we end up having more connections
// than we bargained for. If that's the case, we'll just nip this one right in the bud.
if (_this.activeConnections.length > _this.config.maxConnections) {
connection.peer.destroy();
return;
}
_this._emit('add-connection', connection);
// Send a welcome log message for the warm fuzzies

@@ -722,3 +756,3 @@ _this._broadcastInternal({

delete this._connections[connection.id];
this._emit('destroy-connection', connection.id);
this._emit('destroy-connection', connection);
};

@@ -725,0 +759,0 @@ // Get ANY connection we have, no matter the state it's in.

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

@@ -45,6 +45,5 @@ "main": "./dist/src/index.js",

"dependencies": {
"@browser-network/crypto": "^0.0.1",
"@browser-network/crypto": "^0.0.4",
"@mapbox/node-pre-gyp": "^1.0.9",
"axios": "^0.26.1",
"eccrypto": "^1.1.6",
"simple-peer": "^9.11.1",

@@ -55,3 +54,2 @@ "uuid": "^8.3.2",

"devDependencies": {
"@types/eccrypto": "^1.1.3",
"@types/node": "^16",

@@ -58,0 +56,0 @@ "@types/simple-peer": "^9.11.4",

@@ -30,3 +30,8 @@ # Browser Network

> Therefore is not hampered by the switchboard's ability to simultaneously hold
many websocket connections.
many websocket connections. This is a huge drawback of networks that do use
a websocket switchboard. The network can only be as big as that switchboard's
address space, and if the switchboard goes down, the network will start to fall
apart. They're still distributed networks, but they rely heavily on a single
server entity. This network still relies on a server switchboard, but its self
healing quality leads to a robust network even if the switchboard goes down.

@@ -88,7 +93,15 @@ The Network can be dropped into any web app via

* 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.
This feature can be turned on for more security or off for faster performance if
your network doesn't need to be secure.
* Cryptographic security - Network uses elliptic curve public key encryption to:
- Ensure the veracity of messages: It's cryptographically difficult to
spoof or modify a message that's not your own.
- Encrypt sensitive IP information: The network passes around SDP strings, information
pertaining to WebRTC connections, which contains IP addresses. These are passed
through nodes to get to a node that another is not directly connected to.
To ensure no snooping, ECIES is used to encrypt the SDP information so that only
the recipient it's meant for can read it.
- These features can be turned on for more security or off for faster
performance if your network doesn't care about these security features and
it needs to rapidly send messages, for whatever reason.
### How it works

@@ -99,27 +112,10 @@

Once we connect to another node that's in a network (by we here, I mean a node,
if the reader will allow), then we'll start to hear [messages](#messages) from
our "neighbor" nodes, which is to say, those in the network we're directly
connected to. The messages may originally come from those neighbors or they may not. Each
message has a ttl (time to live). If we receive a message with a ttl > 1, we decrement it and
pass it along on to our neighbors. In this way, the whole network can receive
messages even though not every node is connected to each other.
Once we connect to another node that's in a network we'll start to hear
[messages](#messages) from the whole network, including nodes we're not connected to.
Some of the messages we'll be hearing will be open connection information
(rtc "offer" SDP info). If one of those is for someone we're not yet connected
to, we'll generate a response (rtc "answer" SDP info based on the original offer),
and send that response back out into the network to the node that originally sent it.
If they receive it, a direct connection will be established. It's by this means
that the network is self healing.
Some of the messages we'll be hearing will be from nodes we're not directly
connected to. In that case, the we'll rapidly negotiate a connection with them
by relaying our negotiation via the nodes we are mutually connected to. It's by
this means that the network is self healing.
There are various schemes in place for efficiency.
- Message id memory so as not to repeat rebroadcasts of messages
- A rude list. If you get on the rude list, you get dropped and blocked.
- Connection garbage collection. WebRTC connections are unstable. A garbage
collector periodically cleans bad connections making room for new ones.
- Tunable max connections - dial up or down the max number of connections you want
to have in real time. Network won't make any new connections while there
are more than that setting (`config.maxConnections`).
### The Switching Service

@@ -134,2 +130,8 @@

Once a node is connected to the network, it slows way down on its checking in
with the switchboard. This helps regulate traffic to the switchboard while ensuring
a speedy initial connection with the network. Once that initial connection is made,
the node doesn't need to communicate with the switchboard at all, except to help
future nodes discover the network.
The switching service has negligable processing and memory footprints. It

@@ -174,3 +176,3 @@ operates only in memory, it doesn't need a database or write to disk in any

address: 'my-address-' + Date.now(), // Each window should have its own address, hence the Date.now()
networkId: 'test-network' // Everyone using this id will receive messages from each other
networkId: 'test-network' // Everyone using this id on the same switchboard will receive messages from each other
})

@@ -265,9 +267,3 @@

// See more below...
config:{
offerBroadcastInterval: 1000 * 5,
switchboardRequestInterval: 1000 * 5,
garbageCollectInterval: 1000 * 5,
maxMessageRateBeforeRude: 1000,
maxConnections: 10
}
config: {}
})

@@ -327,6 +323,6 @@ ```

Network also exposes a way to see all of the connections currently established:
Network also exposes a getter to see all of the connections currently established:
```ts
network.connections() // -> Connection[]
network.activeConnections // -> Connection[]
```

@@ -354,2 +350,4 @@

a security vulnerability.
* Ability to export an offer/answer into an easily exchanged, reasonably sized string.
This would allow any switch at all to be used to create a connection, easily, breaking
the reliance on any specific switchboard mechanism.

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