Skip to content

Commit

Permalink
fix(realtime): 💅 Use only global cozySocket
Browse files Browse the repository at this point in the history
  • Loading branch information
CPatchane committed Feb 6, 2019
1 parent f6bc6fb commit d3b62e0
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 223 deletions.
35 changes: 19 additions & 16 deletions packages/cozy-realtime/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* global WebSocket */

// cozySocket is a custom object wrapping logic to websocket and exposing a subscription
// interface
// interface, it's a global variable to avoid creating multiple at a time
let cozySocket

// Important, must match the spec,
Expand All @@ -17,7 +17,9 @@ const RETRY_BASE_DELAY = 1000
// stored as a Map { [doctype]: socket }
let subscriptionsState = new Set()

// getters, for testing
export const getSubscriptionsState = () => subscriptionsState
export const getCozySocket = () => cozySocket

// listener key computing, according to doctype only or with doc id
const LISTENER_KEY_SEPARATOR = '/' // safe since we can't have a '/' in a doctype
Expand All @@ -44,16 +46,15 @@ const hasListeners = socketListeners => {
// only if it is in a ready state. If not, retry a few milliseconds later.
const MAX_SOCKET_POLLS = 500 // to avoid infinite polling
export function subscribeWhenReady(
socket,
doctype,
docId,
remainingTries = MAX_SOCKET_POLLS
) {
if (socket.readyState === WEBSOCKET_STATE.OPEN) {
if (cozySocket.readyState === WEBSOCKET_STATE.OPEN) {
try {
const payload = { type: doctype }
if (docId) payload.id = docId
socket.send(
cozySocket.send(
JSON.stringify({
method: 'SUBSCRIBE',
payload
Expand All @@ -71,7 +72,7 @@ export function subscribeWhenReady(
throw error
} else {
setTimeout(() => {
subscribeWhenReady(socket, doctype, docId, --remainingTries)
subscribeWhenReady(doctype, docId, --remainingTries)
}, 10)
}
}
Expand Down Expand Up @@ -128,7 +129,7 @@ const configTypes = {

const validateConfig = validate(configTypes)

export function connectWebSocket(
export function createWebSocket(
config,
onmessage,
onclose,
Expand Down Expand Up @@ -173,21 +174,19 @@ export function connectWebSocket(
}
socket.onerror = error => console.error(`WebSocket error: ${error.message}`)

cozySocket = socket

if (isRetry && subscriptionsState.size) {
for (let listenerKey of subscriptionsState) {
const { doctype, docId } = getTypeAndIdFromListenerKey(listenerKey)
subscribeWhenReady(socket, doctype, docId)
subscribeWhenReady(doctype, docId)
}
}

return socket
}

export function getCozySocket(config) {
export function initCozySocket(config) {
const listeners = new Map()

let socket

const onSocketMessage = event => {
const data = JSON.parse(event.data)
const eventType = data.event.toLowerCase()
Expand Down Expand Up @@ -235,7 +234,7 @@ export function getCozySocket(config) {
console.warn(`Reconnecting ... ${numRetries} tries left.`)
setTimeout(() => {
try {
socket = connectWebSocket(
createWebSocket(
config,
onSocketMessage,
onSocketClose,
Expand All @@ -249,11 +248,15 @@ export function getCozySocket(config) {
)
}
}, retryDelay)
} else {
console.error(`0 tries left. Stop reconnecting realtime.`)
// remove cached socket
if (cozySocket) cozySocket = null
}
}
}

socket = connectWebSocket(
createWebSocket(
config,
onSocketMessage,
onSocketClose,
Expand All @@ -270,7 +273,7 @@ export function getCozySocket(config) {

if (!listeners.has(listenerKey)) {
listeners.set(listenerKey, {})
subscribeWhenReady(socket, doctype, docId)
subscribeWhenReady(doctype, docId)
}

listeners.set(listenerKey, {
Expand Down Expand Up @@ -308,7 +311,7 @@ export function getCozySocket(config) {

// Returns a subscription to a given doctype (all documents)
export function subscribe(config, doctype, { docId, parse = doc => doc } = {}) {
if (!cozySocket) cozySocket = getCozySocket(config)
if (!cozySocket) initCozySocket(config)
// Some document need to have specific parsing, for example, decoding
// base64 encoded properties
const parseCurried = listener => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`(cozy-realtime) cozySocket handling and getCozySocket: cozySocket should not send socket message and add state multiple times if this is the same doctype 1`] = `
exports[`(cozy-realtime) cozySocket handling and initCozySocket: cozySocket should not send socket message and add state multiple times if this is the same doctype 1`] = `
Set {
"io.cozy.mocks",
}
`;

exports[`(cozy-realtime) cozySocket handling and getCozySocket: cozySocket should send socket message and add state multiple times if this is different doc ids 1`] = `
exports[`(cozy-realtime) cozySocket handling and initCozySocket: cozySocket should send socket message and add state multiple times if this is different doc ids 1`] = `
Set {
"io.cozy.mocks/id1234",
"io.cozy.mocks/id5678",
}
`;

exports[`(cozy-realtime) cozySocket handling and getCozySocket: cozySocket should send socket message and add state multiple times if this is the different doctypes 1`] = `
exports[`(cozy-realtime) cozySocket handling and initCozySocket: cozySocket should send socket message and add state multiple times if this is the different doctypes 1`] = `
Set {
"io.cozy.mocks",
"io.cozy.mocks2",
"io.cozy.mocks3",
}
`;

exports[`(cozy-realtime) cozySocket handling and getCozySocket: cozySocket should throw an error if the listener provided is not a function 1`] = `"Realtime event listener must be a function"`;
exports[`(cozy-realtime) cozySocket handling and initCozySocket: cozySocket should throw an error if the listener provided is not a function 1`] = `"Realtime event listener must be a function"`;

exports[`(cozy-realtime) cozySocket handling and getCozySocket: getCozySocket should call connectWebSocket with correct config and arguments 1`] = `
exports[`(cozy-realtime) cozySocket handling and initCozySocket: initCozySocket should call createWebSocket with correct config and arguments 1`] = `
Array [
Object {
"domain": "cozy.tools:8080",
Expand All @@ -37,13 +37,13 @@ Array [
]
`;

exports[`(cozy-realtime) cozySocket handling and getCozySocket: getCozySocket should return a configured cozy socket 1`] = `
exports[`(cozy-realtime) cozySocket handling and initCozySocket: initCozySocket should return a configured cozy socket 1`] = `
Object {
"subscribe": [Function],
"unsubscribe": [Function],
}
`;

exports[`(cozy-realtime) cozySocket handling and getCozySocket: onSocketClose provided by getCozySocket to connectWebSocket should handle error from a retry connectWebSocket with an error message 1`] = `"Unable to reconnect to realtime. Error: expected socket retry error"`;
exports[`(cozy-realtime) cozySocket handling and initCozySocket: onSocketClose provided by initCozySocket to createWebSocket should handle error from a retry createWebSocket with an error message 1`] = `"Unable to reconnect to realtime. Error: expected socket retry error"`;

exports[`(cozy-realtime) cozySocket handling and getCozySocket: onSocketMessage provided by getCozySocket to connectWebSocket should throw error if eventType error 1`] = `"expected realtime error"`;
exports[`(cozy-realtime) cozySocket handling and initCozySocket: onSocketMessage provided by initCozySocket to createWebSocket should throw error if eventType error 1`] = `"expected realtime error"`;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`(cozy-realtime) connectWebSocket: socket should create and return a cozySocket handling wss 1`] = `
exports[`(cozy-realtime) createWebSocket: socket should create and return a cozySocket handling wss 1`] = `
WebSocket {
"binaryType": "blob",
"listeners": Object {
Expand All @@ -23,14 +23,14 @@ WebSocket {
}
`;

exports[`(cozy-realtime) connectWebSocket: socket should handle authenticating on socket open 1`] = `
exports[`(cozy-realtime) createWebSocket: socket should handle authenticating on socket open 1`] = `
Object {
"method": "AUTH",
"payload": "blablablatoken",
}
`;

exports[`(cozy-realtime) connectWebSocket: socket should handle closing server 1`] = `
exports[`(cozy-realtime) createWebSocket: socket should handle closing server 1`] = `
CloseEvent {
"bubbles": false,
"cancelBubble": false,
Expand Down Expand Up @@ -107,7 +107,7 @@ CloseEvent {
}
`;

exports[`(cozy-realtime) connectWebSocket: socket should handle message 1`] = `
exports[`(cozy-realtime) createWebSocket: socket should handle message 1`] = `
MessageEvent {
"bubbles": false,
"cancelable": false,
Expand Down Expand Up @@ -221,116 +221,36 @@ MessageEvent {
}
`;

exports[`(cozy-realtime) connectWebSocket: socket should send doctype and docId subscriptions again if this is a retry and if there are subscriptionsState 1`] = `
exports[`(cozy-realtime) createWebSocket: socket should send doctype and docId subscriptions again if this is a retry and if there are subscriptionsState 1`] = `
Array [
Array [
WebSocket {
"binaryType": "blob",
"listeners": Object {
"close": Array [
[Function],
],
"error": Array [
[Function],
],
"message": Array [
[MockFunction],
],
"open": Array [
[Function],
],
},
"protocol": "io.cozy.websocket",
"readyState": 0,
"url": "ws://localhost:8880/realtime/",
},
"io.cozy.mocks",
"id1234",
],
Array [
WebSocket {
"binaryType": "blob",
"listeners": Object {
"close": Array [
[Function],
],
"error": Array [
[Function],
],
"message": Array [
[MockFunction],
],
"open": Array [
[Function],
],
},
"protocol": "io.cozy.websocket",
"readyState": 0,
"url": "ws://localhost:8880/realtime/",
},
"io.cozy.mocks2",
null,
],
]
`;

exports[`(cozy-realtime) connectWebSocket: socket should send doctype subscriptions again if this is a retry and if there are subscriptionsState 1`] = `
exports[`(cozy-realtime) createWebSocket: socket should send doctype subscriptions again if this is a retry and if there are subscriptionsState 1`] = `
Array [
Array [
WebSocket {
"binaryType": "blob",
"listeners": Object {
"close": Array [
[Function],
],
"error": Array [
[Function],
],
"message": Array [
[MockFunction],
],
"open": Array [
[Function],
],
},
"protocol": "io.cozy.websocket",
"readyState": 0,
"url": "ws://localhost:8880/realtime/",
},
"io.cozy.mocks",
null,
],
Array [
WebSocket {
"binaryType": "blob",
"listeners": Object {
"close": Array [
[Function],
],
"error": Array [
[Function],
],
"message": Array [
[MockFunction],
],
"open": Array [
[Function],
],
},
"protocol": "io.cozy.websocket",
"readyState": 0,
"url": "ws://localhost:8880/realtime/",
},
"io.cozy.mocks2",
null,
],
]
`;

exports[`(cozy-realtime) connectWebSocket: socket should throw error if no url or domain provided 1`] = `"domain is required if no attribute url are provider.."`;
exports[`(cozy-realtime) createWebSocket: socket should throw error if no url or domain provided 1`] = `"domain is required if no attribute url are provider.."`;

exports[`(cozy-realtime) connectWebSocket: socket should throw error if wrong url format provided 1`] = `"Unable to detect domain"`;
exports[`(cozy-realtime) createWebSocket: socket should throw error if wrong url format provided 1`] = `"Unable to detect domain"`;

exports[`(cozy-realtime) connectWebSocket: socket should throw error if wrong url type provided 1`] = `"url should be an URL."`;
exports[`(cozy-realtime) createWebSocket: socket should throw error if wrong url type provided 1`] = `"url should be an URL."`;

exports[`(cozy-realtime) connectWebSocket: socket should warn errors on socket errors 1`] = `"WebSocket error: undefined"`;
exports[`(cozy-realtime) createWebSocket: socket should warn errors on socket errors 1`] = `"WebSocket error: undefined"`;

0 comments on commit d3b62e0

Please sign in to comment.