Skip to content

Commit 72aa169

Browse files
committed
feat: more robust datachannel management
- Handle firewalls that close mapping ports quickly - Sensible client and server initiated keep-alive pings when there is no regular http traffic to proxy
1 parent 4b6da03 commit 72aa169

File tree

3 files changed

+94
-28
lines changed

3 files changed

+94
-28
lines changed

Diff for: src/remote/edgeAPI.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class EdgeAPI {
2626

2727
async _getJSON (request) {
2828
const response = await this._get(request)
29-
const jsn = this.pnp.peerFetch.jsonify(response)
29+
const jsn = this.pnp.peerFetch.jsonify(response.content)
3030
return jsn
3131
}
3232

@@ -64,7 +64,7 @@ export class EdgeAPI {
6464
var imageUrl
6565
try {
6666
const response = await this._get(request)
67-
var arrayBufferView = new Uint8Array(response)
67+
var arrayBufferView = new Uint8Array(response.content)
6868
var blob = new Blob([arrayBufferView])
6969
var urlCreator = window.URL || window.webkitURL
7070
imageUrl = urlCreator.createObjectURL(blob)

Diff for: src/remote/peer-fetch.js

+82-20
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,36 @@ export class PeerFetch {
1717
// Requests are processed in FIFO order.
1818
this._nextTicketInLine = 0
1919
this._configureDataConnection()
20+
this._schedulePing()
21+
}
22+
23+
/**
24+
* Schedule periodic pings to keep the datachannel alive.
25+
* Some routers and firewalls close open ports within seconds
26+
* without data packets flowing through.
27+
*/
28+
_schedulePing () {
29+
this._keepAlive = setInterval(
30+
async () => {
31+
// check if there are any pending requests
32+
// no ping needed as long as there is traffic on the channel
33+
if (!this._pendingRequests()) {
34+
// request top page
35+
const request = {
36+
url: 'ping'
37+
}
38+
await this.get(request)
39+
}
40+
},
41+
1000 // every second
42+
)
43+
}
44+
45+
/**
46+
* Stop keepalive pings.
47+
*/
48+
_stopPing () {
49+
clearInterval(this._keepAlive)
2050
}
2151

2252
/**
@@ -46,28 +76,48 @@ export class PeerFetch {
4676
// Handle incoming data (messages only since this is the signal sender)
4777
const peerFetch = this
4878
this._dataConnection.on('data', function (data) {
49-
console.debug('Remote 11111 Peer Data message received (type %s): %s',
50-
typeof (data), data)
79+
console.debug('Remote Peer Data message received (type %s)',
80+
typeof (data), { data })
5181
// we expect data to be a response to a previously sent request message
52-
const response = data
5382
const ticket = peerFetch._nextTicketInLine
54-
console.debug(peerFetch, peerFetch._requestMap, ticket, response)
55-
// const blah = {
56-
// url: 'http://localhost:8778/?from=_dataConnection.on_data'
57-
// }
58-
// const msg = JSON.stringify(blah)
59-
// const dc = peerFetch._dataConnection
60-
// console.error('>>>>>>>>>>>>>>>>>> Sending msg', { dc, msg })
61-
// peerFetch._dataConnection.send(msg)
62-
// update request map entry with this response
83+
console.debug(peerFetch, peerFetch._requestMap, ticket, data)
84+
// update request-response map entry with this response
6385
const pair = peerFetch._requestMap.get(ticket)
6486
if (pair) {
65-
pair.response = response
87+
if (!pair.response) {
88+
console.debug('Processing response header')
89+
// this is the first data message from the responses
90+
const header = peerFetch.jsonify(data)
91+
if (header.status === 202) {
92+
console.debug('Received keepalive ping')
93+
// server accepted the request but still working
94+
// ignore and keep waiting until result or timeout
95+
} else {
96+
console.debug('Received web server final response header')
97+
// save header part of the response
98+
// and wait for the p2p data messages with the content body
99+
const receivedAll = false
100+
pair.response = { header, receivedAll }
101+
}
102+
} else {
103+
console.debug('Processing response content')
104+
// response content body arrived
105+
pair.response.content = data
106+
// assume for now that all response content can fit
107+
// in a single 64KB data message
108+
pair.response.receivedAll = true
109+
}
66110
} else {
67111
console.error('No entry found in pending requestMap for ticket',
68112
{ ticket })
69113
}
70114
})
115+
this._dataConnection.on('open', function () {
116+
peerFetch._schedulePing()
117+
})
118+
this._dataConnection.on('close', function () {
119+
peerFetch._stopPing()
120+
})
71121
}
72122

73123
/**
@@ -133,11 +183,20 @@ export class PeerFetch {
133183
const ticket = this._nextTicketInLine
134184
// check if there is a pending ticket
135185
// and process it
136-
if (this._nextTicketInLine < this._nextAvailableTicket) {
186+
if (this._pendingRequests()) {
137187
this._sendNextRequest(ticket)
138188
}
139189
}
140190

191+
/**
192+
* Check if there are any pending requests waiting in line.
193+
*/
194+
_pendingRequests () {
195+
if (this._nextTicketInLine < this._nextAvailableTicket) {
196+
return true
197+
}
198+
}
199+
141200
textDecode (arrayBuffer) {
142201
let decodedString
143202
if ('TextDecoder' in window) {
@@ -154,8 +213,13 @@ export class PeerFetch {
154213
return decodedString
155214
}
156215

157-
jsonify (arrayBuffer) {
158-
const decodedString = this.textDecode(arrayBuffer)
216+
jsonify (data) {
217+
let decodedString
218+
if (typeof data === 'string') {
219+
decodedString = data
220+
} else {
221+
decodedString = this.textDecode(data)
222+
}
159223
const response = JSON.parse(decodedString)
160224
return response
161225
}
@@ -167,18 +231,16 @@ export class PeerFetch {
167231
let request, response
168232
do {
169233
({ request, response } = this._requestMap.get(ticket))
170-
if (response) {
234+
if (response && response.receivedAll) {
171235
// if (typeof(response) === 'string') {
172236
this._ticketProcessed(ticket)
173237
console.debug('Received response', { ticket, request, response })
174238
// schedule processing of next request shortly
175239
setTimeout(() => this._processNextTicketInLine(), 50)
176240
return response
177-
} else {
178-
console.debug('Waiting for response', { ticket, request })
179-
// this._processNextTicketInLine()
180241
}
181242
timeElapsed = Date.now() - timerStart
243+
console.debug('Waiting for response', { ticket, request, timeElapsed })
182244
await sleep(3000)
183245
} while (!response && timeElapsed < timeout)
184246
throw Error('PeerFetch Timeout while waiting for response.')

Diff for: src/store/pnp.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -400,11 +400,16 @@ const actions = {
400400
url: 'http://localhost:8778'
401401
}
402402
const response = await state.peerFetch.get(request)
403-
const text = state.peerFetch.textDecode(response)
404-
console.log('peerFetch.get returned response', { request, response, text })
405-
// if data is authentication challenge response, verify it
406-
// for now we naively check for Ambianic in the response.
407-
const authPassed = text.includes('Ambianic')
403+
console.log('PEER_AUTHENTICATE', { request, response })
404+
let authPassed = false
405+
if (response.header.status === 200) {
406+
console.log('PEER_AUTHENTICATE status OK')
407+
const text = state.peerFetch.textDecode(response.content)
408+
// if data is authentication challenge response, verify it
409+
// for now we naively check for Ambianic in the response.
410+
authPassed = text.includes('Ambianic')
411+
console.log(`PEER_AUTHENTICATE response body OK = ${authPassed}`)
412+
}
408413
if (authPassed) {
409414
// console.debug('Remote peer authenticated as:', authMessage.name)
410415
commit(PEER_CONNECTED, peerConnection)
@@ -415,7 +420,6 @@ const actions = {
415420
commit(USER_MESSAGE, 'Remote peer authentication failed.')
416421
commit(PEER_CONNECTION_ERROR)
417422
}
418-
console.debug('Peer DataConnection sending message', request)
419423
console.debug('DataChannel transport capabilities',
420424
peerConnection.dataChannel)
421425
//

0 commit comments

Comments
 (0)