Skip to content

Commit 9b13fe3

Browse files
authored
fix: remove node global (#587)
* fix: use new libp2p-crypto * fix: remove node globals and reduce size - adds buffer require - adds globalThis where needed - streaming-iterables was remove and new utils created, this will be consolidated in `ipfs-utils` and backported here to normalize all these iterator helper functions - latency-monitor was copied inside the repo - iso-random-stream is now used instead of node crypto - the test `should ignore self on discovery` was moved to node only Size: 172.97KB 47.03KB below the 220KB limit. `aegir build --node false` and `aegir test -t browser --node false` now work 🎉 * fix: fix require path * fix: feedback * fix: update deps and bundle size * chore: bump interfaces * chore: update size
1 parent 1f85e02 commit 9b13fe3

20 files changed

+419
-48
lines changed

.aegir.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const after = async () => {
4545
}
4646

4747
module.exports = {
48-
bundlesize: { maxSize: '220kB' },
48+
bundlesize: { maxSize: '179kB' },
4949
hooks: {
5050
pre: before,
5151
post: after

examples/pnet/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint no-console: ["off"] */
22
'use strict'
33

4+
const { Buffer } = require('buffer')
45
const { generate } = require('libp2p/src/pnet')
56
const privateLibp2pNode = require('./libp2p-node')
67

@@ -17,7 +18,7 @@ generate(otherSwarmKey)
1718
;(async () => {
1819
const node1 = await privateLibp2pNode(swarmKey)
1920

20-
// TASK: switch the commented out line below so we're using a different key, to see the nodes fail to connect
21+
// TASK: switch the commented out line below so we're using a different key, to see the nodes fail to connect
2122
const node2 = await privateLibp2pNode(swarmKey)
2223
// const node2 = await privateLibp2pNode(otherSwarmKey)
2324

@@ -47,4 +48,4 @@ generate(otherSwarmKey)
4748
['This message is sent on a private network'],
4849
stream
4950
)
50-
})();
51+
})()

examples/pubsub/1.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-console */
22
'use strict'
33

4+
const { Buffer } = require('buffer')
45
const Libp2p = require('../../')
56
const TCP = require('libp2p-tcp')
67
const Mplex = require('libp2p-mplex')
@@ -31,7 +32,7 @@ const createNode = async () => {
3132

3233
const [node1, node2] = await Promise.all([
3334
createNode(),
34-
createNode(),
35+
createNode()
3536
])
3637

3738
await node1.dial(node2.peerInfo)
@@ -48,4 +49,4 @@ const createNode = async () => {
4849
setInterval(() => {
4950
node2.pubsub.publish(topic, Buffer.from('Bird bird bird, bird is the word!'))
5051
}, 1000)
51-
})();
52+
})()

package.json

+18-17
Original file line numberDiff line numberDiff line change
@@ -48,37 +48,39 @@
4848
"class-is": "^1.1.0",
4949
"debug": "^4.1.1",
5050
"err-code": "^2.0.0",
51+
"events": "^3.1.0",
5152
"hashlru": "^2.3.0",
53+
"ipfs-utils": "^2.2.0",
5254
"it-all": "^1.0.1",
53-
"it-buffer": "^0.1.1",
55+
"it-buffer": "^0.1.2",
5456
"it-handshake": "^1.0.1",
55-
"it-length-prefixed": "^3.0.0",
57+
"it-length-prefixed": "^3.0.1",
5658
"it-pipe": "^1.1.0",
5759
"it-protocol-buffers": "^0.2.0",
58-
"latency-monitor": "~0.2.1",
59-
"libp2p-crypto": "^0.17.1",
60-
"libp2p-interfaces": "^0.2.3",
60+
"libp2p-crypto": "^0.17.6",
61+
"libp2p-interfaces": "^0.2.8",
6162
"libp2p-utils": "^0.1.2",
6263
"mafmt": "^7.0.0",
6364
"merge-options": "^2.0.0",
6465
"moving-average": "^1.0.0",
65-
"multiaddr": "^7.2.1",
66+
"multiaddr": "^7.4.3",
6667
"multistream-select": "^0.15.0",
6768
"mutable-proxy": "^1.0.0",
6869
"p-any": "^3.0.0",
6970
"p-fifo": "^1.0.0",
70-
"p-settle": "^4.0.0",
71-
"peer-id": "^0.13.4",
71+
"p-settle": "^4.0.1",
72+
"peer-id": "^0.13.11",
7273
"peer-info": "^0.17.0",
7374
"protons": "^1.0.1",
7475
"retimer": "^2.0.0",
76+
"streaming-iterables": "^4.1.0",
7577
"timeout-abort-controller": "^1.0.0",
7678
"xsalsa20": "^1.0.2"
7779
},
7880
"devDependencies": {
7981
"@nodeutils/defaults-deep": "^1.1.0",
8082
"abortable-iterator": "^3.0.0",
81-
"aegir": "^21.3.0",
83+
"aegir": "^21.9.0",
8284
"chai": "^4.2.0",
8385
"chai-as-promised": "^7.1.1",
8486
"cids": "^0.8.0",
@@ -92,20 +94,19 @@
9294
"libp2p-delegated-content-routing": "^0.4.5",
9395
"libp2p-delegated-peer-routing": "^0.4.3",
9496
"libp2p-floodsub": "^0.20.0",
95-
"libp2p-gossipsub": "^0.2.0",
96-
"libp2p-kad-dht": "^0.18.2",
97+
"libp2p-gossipsub": "^0.2.6",
98+
"libp2p-kad-dht": "^0.18.6",
9799
"libp2p-mdns": "^0.13.0",
98-
"libp2p-mplex": "^0.9.1",
99-
"libp2p-secio": "^0.12.1",
100+
"libp2p-mplex": "^0.9.5",
101+
"libp2p-secio": "^0.12.4",
100102
"libp2p-tcp": "^0.14.1",
101-
"libp2p-webrtc-star": "^0.17.0",
103+
"libp2p-webrtc-star": "^0.17.9",
102104
"libp2p-websockets": "^0.13.1",
103-
"nock": "^12.0.0",
105+
"nock": "^12.0.3",
104106
"p-defer": "^3.0.0",
105107
"p-times": "^2.1.0",
106108
"p-wait-for": "^3.1.0",
107-
"sinon": "^9.0.0",
108-
"streaming-iterables": "^4.1.0",
109+
"sinon": "^9.0.2",
109110
"wrtc": "^0.4.1"
110111
},
111112
"contributors": [

src/connection-manager/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const errcode = require('err-code')
44
const mergeOptions = require('merge-options')
5-
const LatencyMonitor = require('latency-monitor').default
5+
const LatencyMonitor = require('./latency-monitor')
66
const debug = require('debug')('libp2p:connection-manager')
77
const retimer = require('retimer')
88

+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
'use strict'
2+
3+
/**
4+
* This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE)
5+
*/
6+
7+
/* global window */
8+
const globalThis = require('ipfs-utils/src/globalthis')
9+
const EventEmitter = require('events')
10+
const VisibilityChangeEmitter = require('./visibility-change-emitter')
11+
const debug = require('debug')('latency-monitor:LatencyMonitor')
12+
13+
/**
14+
* @typedef {Object} SummaryObject
15+
* @property {Number} events How many events were called
16+
* @property {Number} minMS What was the min time for a cb to be called
17+
* @property {Number} maxMS What was the max time for a cb to be called
18+
* @property {Number} avgMs What was the average time for a cb to be called
19+
* @property {Number} lengthMs How long this interval was in ms
20+
*/
21+
22+
/**
23+
* A class to monitor latency of any async function which works in a browser or node. This works by periodically calling
24+
* the asyncTestFn and timing how long it takes the callback to be called. It can also periodically emit stats about this.
25+
* This can be disabled and stats can be pulled via setting dataEmitIntervalMs = 0.
26+
*
27+
* The default implementation is an event loop latency monitor. This works by firing periodic events into the event loop
28+
* and timing how long it takes to get back.
29+
*
30+
* @example
31+
* const monitor = new LatencyMonitor();
32+
* monitor.on('data', (summary) => console.log('Event Loop Latency: %O', summary));
33+
*
34+
* @example
35+
* const monitor = new LatencyMonitor({latencyCheckIntervalMs: 1000, dataEmitIntervalMs: 60000, asyncTestFn:ping});
36+
* monitor.on('data', (summary) => console.log('Ping Pong Latency: %O', summary));
37+
*/
38+
class LatencyMonitor extends EventEmitter {
39+
/**
40+
* @param {Number} [latencyCheckIntervalMs=500] How often to add a latency check event (ms)
41+
* @param {Number} [dataEmitIntervalMs=5000] How often to summarize latency check events. null or 0 disables event firing
42+
* @param {function} [asyncTestFn] What cb-style async function to use
43+
* @param {Number} [latencyRandomPercentage=5] What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events.
44+
*/
45+
constructor ({ latencyCheckIntervalMs, dataEmitIntervalMs, asyncTestFn, latencyRandomPercentage } = {}) {
46+
super()
47+
const that = this
48+
49+
// 0 isn't valid here, so its ok to use ||
50+
that.latencyCheckIntervalMs = latencyCheckIntervalMs || 500 // 0.5s
51+
that.latencyRandomPercentage = latencyRandomPercentage || 10
52+
that._latecyCheckMultiply = 2 * (that.latencyRandomPercentage / 100.0) * that.latencyCheckIntervalMs
53+
that._latecyCheckSubtract = that._latecyCheckMultiply / 2
54+
55+
that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) ? undefined
56+
: dataEmitIntervalMs || 5 * 1000 // 5s
57+
debug('latencyCheckIntervalMs: %s dataEmitIntervalMs: %s',
58+
that.latencyCheckIntervalMs, that.dataEmitIntervalMs)
59+
if (that.dataEmitIntervalMs) {
60+
debug('Expecting ~%s events per summary', that.latencyCheckIntervalMs / that.dataEmitIntervalMs)
61+
} else {
62+
debug('Not emitting summaries')
63+
}
64+
65+
that.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency
66+
67+
// If process: use high resolution timer
68+
if (globalThis.process && globalThis.process.hrtime) {
69+
debug('Using process.hrtime for timing')
70+
that.now = globalThis.process.hrtime
71+
that.getDeltaMS = (startTime) => {
72+
const hrtime = that.now(startTime)
73+
return (hrtime[0] * 1000) + (hrtime[1] / 1000000)
74+
}
75+
// Let's try for a timer that only monotonically increases
76+
} else if (typeof window !== 'undefined' && window.performance && window.performance.now) {
77+
debug('Using performance.now for timing')
78+
that.now = window.performance.now.bind(window.performance)
79+
that.getDeltaMS = (startTime) => Math.round(that.now() - startTime)
80+
} else {
81+
debug('Using Date.now for timing')
82+
that.now = Date.now
83+
that.getDeltaMS = (startTime) => that.now() - startTime
84+
}
85+
86+
that._latencyData = that._initLatencyData()
87+
88+
// We check for isBrowser because of browsers set max rates of timeouts when a page is hidden,
89+
// so we fall back to another library
90+
// See: http://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs
91+
if (isBrowser()) {
92+
that._visibilityChangeEmitter = new VisibilityChangeEmitter()
93+
that._visibilityChangeEmitter.on('visibilityChange', (pageInFocus) => {
94+
if (pageInFocus) {
95+
that._startTimers()
96+
} else {
97+
that._emitSummary()
98+
that._stopTimers()
99+
}
100+
})
101+
}
102+
103+
if (!that._visibilityChangeEmitter || that._visibilityChangeEmitter.isVisible()) {
104+
that._startTimers()
105+
}
106+
}
107+
108+
/**
109+
* Start internal timers
110+
* @private
111+
*/
112+
_startTimers () {
113+
// Timer already started, ignore this
114+
if (this._checkLatencyID) {
115+
return
116+
}
117+
this._checkLatency()
118+
if (this.dataEmitIntervalMs) {
119+
this._emitIntervalID = setInterval(() => this._emitSummary(), this.dataEmitIntervalMs)
120+
if (typeof this._emitIntervalID.unref === 'function') {
121+
this._emitIntervalID.unref() // Doesn't block exit
122+
}
123+
}
124+
}
125+
126+
/**
127+
* Stop internal timers
128+
* @private
129+
*/
130+
_stopTimers () {
131+
if (this._checkLatencyID) {
132+
clearTimeout(this._checkLatencyID)
133+
this._checkLatencyID = undefined
134+
}
135+
if (this._emitIntervalID) {
136+
clearInterval(this._emitIntervalID)
137+
this._emitIntervalID = undefined
138+
}
139+
}
140+
141+
/**
142+
* Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show
143+
* @private
144+
*/
145+
_emitSummary () {
146+
const summary = this.getSummary()
147+
if (summary.events > 0) {
148+
this.emit('data', summary)
149+
}
150+
}
151+
152+
/**
153+
* Calling this function will end the collection period. If a timing event was already fired and somewhere in the queue,
154+
* it will not count for this time period
155+
* @returns {SummaryObject}
156+
*/
157+
getSummary () {
158+
// We might want to adjust for the number of expected events
159+
// Example: first 1 event it comes back, then such a long blocker that the next emit check comes
160+
// Then this fires - looks like no latency!!
161+
const latency = {
162+
events: this._latencyData.events,
163+
minMs: this._latencyData.minMs,
164+
maxMs: this._latencyData.maxMs,
165+
avgMs: this._latencyData.events ? this._latencyData.totalMs / this._latencyData.events
166+
: Number.POSITIVE_INFINITY,
167+
lengthMs: this.getDeltaMS(this._latencyData.startTime)
168+
}
169+
this._latencyData = this._initLatencyData() // Clear
170+
171+
debug('Summary: %O', latency)
172+
return latency
173+
}
174+
175+
/**
176+
* Randomly calls an async fn every roughly latencyCheckIntervalMs (plus some randomness). If no async fn is found,
177+
* it will simply report on event loop latency.
178+
*
179+
* @private
180+
*/
181+
_checkLatency () {
182+
const that = this
183+
// Randomness is needed to avoid alignment by accident to regular things in the event loop
184+
const randomness = (Math.random() * that._latecyCheckMultiply) - that._latecyCheckSubtract
185+
186+
// We use this to ensure that in case some overlap somehow, we don't take the wrong startTime/offset
187+
const localData = {
188+
deltaOffset: Math.ceil(that.latencyCheckIntervalMs + randomness),
189+
startTime: that.now()
190+
}
191+
192+
const cb = () => {
193+
// We are already stopped, ignore this datapoint
194+
if (!this._checkLatencyID) {
195+
return
196+
}
197+
const deltaMS = that.getDeltaMS(localData.startTime) - localData.deltaOffset
198+
that._checkLatency() // Start again ASAP
199+
200+
// Add the data point. If this gets complex, refactor it
201+
that._latencyData.events++
202+
that._latencyData.minMs = Math.min(that._latencyData.minMs, deltaMS)
203+
that._latencyData.maxMs = Math.max(that._latencyData.maxMs, deltaMS)
204+
that._latencyData.totalMs += deltaMS
205+
debug('MS: %s Data: %O', deltaMS, that._latencyData)
206+
}
207+
debug('localData: %O', localData)
208+
209+
this._checkLatencyID = setTimeout(() => {
210+
// This gets rid of including event loop
211+
if (that.asyncTestFn) {
212+
// Clear timing related things
213+
localData.deltaOffset = 0
214+
localData.startTime = that.now()
215+
that.asyncTestFn(cb)
216+
} else {
217+
// setTimeout is not more accurate than 1ms, so this will ensure positive numbers. Add 1 to emitted data to remove.
218+
// This is not the best, but for now it'll be just fine. This isn't meant to be sub ms accurate.
219+
localData.deltaOffset -= 1
220+
// If there is no function to test, we mean check latency which is a special case that is really cb => cb()
221+
// We avoid that for the few extra function all overheads. Also, we want to keep the timers different
222+
cb()
223+
}
224+
}, localData.deltaOffset)
225+
226+
if (typeof this._checkLatencyID.unref === 'function') {
227+
this._checkLatencyID.unref() // Doesn't block exit
228+
}
229+
}
230+
231+
_initLatencyData () {
232+
return {
233+
startTime: this.now(),
234+
minMs: Number.POSITIVE_INFINITY,
235+
maxMs: Number.NEGATIVE_INFINITY,
236+
events: 0,
237+
totalMs: 0
238+
}
239+
}
240+
}
241+
242+
function isBrowser () {
243+
return typeof window !== 'undefined'
244+
}
245+
246+
module.exports = LatencyMonitor

0 commit comments

Comments
 (0)