fix(sync): wait for websocket full connection

Websocket has now a "connect" method, which is a promise,
that yields "onconnect", so the engine can wait for it before
proceding.
This commit is contained in:
Yohan Boniface 2025-02-11 13:26:27 +01:00
parent 1ce491a70c
commit 414cc805c2
2 changed files with 51 additions and 46 deletions

View file

@ -75,19 +75,20 @@ export class SyncEngine {
this.reconnect() this.reconnect()
return return
} }
this.start(response.token) await this.start(response.token)
} }
start(authToken) { async start(authToken) {
const path = this._umap.urls.get('ws_sync', { map_id: this._umap.id }) const path = this._umap.urls.get('ws_sync', { map_id: this._umap.id })
const protocol = window.location.protocol === 'http:' ? 'ws:' : 'wss:' const protocol = window.location.protocol === 'http:' ? 'ws:' : 'wss:'
this.transport = new WebSocketTransport( this.transport = new WebSocketTransport(this)
await this.transport.connect(
`${protocol}//${window.location.host}${path}`, `${protocol}//${window.location.host}${path}`,
authToken, authToken,
this,
this.peerId, this.peerId,
this._umap.properties.user?.name this._umap.properties.user?.name
) )
this.onConnection()
} }
stop() { stop() {
@ -108,11 +109,11 @@ export class SyncEngine {
this.websocketConnected = false this.websocketConnected = false
this.updaters.map.update({ key: 'numberOfConnectedPeers' }) this.updaters.map.update({ key: 'numberOfConnectedPeers' })
this._reconnectTimeout = setTimeout(() => { this._reconnectTimeout = setTimeout(async () => {
if (this._reconnectDelay < MAX_RECONNECT_DELAY) { if (this._reconnectDelay < MAX_RECONNECT_DELAY) {
this._reconnectDelay = this._reconnectDelay * RECONNECT_DELAY_FACTOR this._reconnectDelay = this._reconnectDelay * RECONNECT_DELAY_FACTOR
} }
this.authenticate() await this.authenticate()
}, this._reconnectDelay) }, this._reconnectDelay)
} }
upsert(subject, metadata, value) { upsert(subject, metadata, value) {

View file

@ -3,53 +3,57 @@ const PING_INTERVAL = 30000
const FIRST_CONNECTION_TIMEOUT = 2000 const FIRST_CONNECTION_TIMEOUT = 2000
export class WebSocketTransport { export class WebSocketTransport {
constructor(webSocketURI, authToken, messagesReceiver, peerId, username) { constructor(messagesReceiver) {
this.receiver = messagesReceiver this.receiver = messagesReceiver
}
this.websocket = new WebSocket(webSocketURI) async connect(webSocketURI, authToken, peerId, username) {
return new Promise((resolve, reject) => {
this.websocket = new WebSocket(webSocketURI)
this.websocket.onopen = () => { this.websocket.onopen = () => {
this.send('JoinRequest', { token: authToken, peer: peerId, username }) this.send('JoinRequest', { token: authToken, peer: peerId, username })
this.receiver.onConnection() resolve(this.websocket)
}
this.websocket.addEventListener('message', this.onMessage.bind(this))
this.websocket.onclose = () => {
console.log('websocket closed')
if (!this.receiver.closeRequested) {
console.log('Not requested, reconnecting...')
this.receiver.reconnect()
} }
} this.websocket.addEventListener('message', this.onMessage.bind(this))
this.websocket.onclose = () => {
this.websocket.onerror = (error) => { console.log('websocket closed')
console.log('WS ERROR', error) if (!this.receiver.closeRequested) {
} console.log('Not requested, reconnecting...')
this.receiver.reconnect()
this.ensureOpen = setInterval(() => { }
if (this.websocket.readyState !== WebSocket.OPEN) {
this.websocket.close()
clearInterval(this.ensureOpen)
} }
}, FIRST_CONNECTION_TIMEOUT)
// To ensure the connection is still alive, we send ping and expect pong back. this.websocket.onerror = (error) => {
// Websocket provides a `ping` method to keep the connection alive, but it's console.log('WS ERROR', error)
// unfortunately not possible to access it from the WebSocket object.
// See https://making.close.com/posts/reliable-websockets/ for more details.
this.pingInterval = setInterval(() => {
if (this.websocket.readyState === WebSocket.OPEN) {
console.log('sending ping')
this.websocket.send('ping')
this.pongReceived = false
setTimeout(() => {
if (!this.pongReceived) {
console.warn('No pong received, reconnecting...')
this.websocket.close()
clearInterval(this.pingInterval)
}
}, PONG_TIMEOUT)
} }
}, PING_INTERVAL)
this.ensureOpen = setInterval(() => {
if (this.websocket.readyState !== WebSocket.OPEN) {
this.websocket.close()
clearInterval(this.ensureOpen)
}
}, FIRST_CONNECTION_TIMEOUT)
// To ensure the connection is still alive, we send ping and expect pong back.
// Websocket provides a `ping` method to keep the connection alive, but it's
// unfortunately not possible to access it from the WebSocket object.
// See https://making.close.com/posts/reliable-websockets/ for more details.
this.pingInterval = setInterval(() => {
if (this.websocket.readyState === WebSocket.OPEN) {
console.log('sending ping')
this.websocket.send('ping')
this.pongReceived = false
setTimeout(() => {
if (!this.pongReceived) {
console.warn('No pong received, reconnecting...')
this.websocket.close()
clearInterval(this.pingInterval)
}
}, PONG_TIMEOUT)
}
}, PING_INTERVAL)
})
} }
onMessage(wsMessage) { onMessage(wsMessage) {