@liveblocks/react
Advanced tools
Comparing version 0.6.0-beta.3 to 0.6.0-beta.4
773
lib/index.js
@@ -44,3 +44,2 @@ Object.defineProperty(exports, '__esModule', { value: true }); | ||
WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["INTERNAL_ERROR"] = 4005] = "INTERNAL_ERROR"; | ||
})(WebsocketCloseCodes || (WebsocketCloseCodes = {})); | ||
@@ -559,9 +558,2 @@ | ||
})(LiveStorageState || (LiveStorageState = {})); | ||
var RoomState; | ||
(function (RoomState) { | ||
RoomState[RoomState["Default"] = 0] = "Default"; | ||
RoomState[RoomState["Connecting"] = 1] = "Connecting"; | ||
RoomState[RoomState["Connected"] = 2] = "Connected"; | ||
RoomState[RoomState["Error"] = 3] = "Error"; | ||
})(RoomState || (RoomState = {})); | ||
@@ -661,21 +653,11 @@ function remove(array, item) { | ||
}; | ||
const BACKOFF_RETRY_DELAYS = [ | ||
250, | ||
500, | ||
1000, | ||
2000, | ||
4000, | ||
8000, | ||
10000, | ||
10000, | ||
10000, | ||
10000, | ||
]; | ||
const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000]; | ||
const HEARTBEAT_INTERVAL = 30000; | ||
// const WAKE_UP_CHECK_INTERVAL = 2000; | ||
const PONG_TIMEOUT = 2000; | ||
function isValidRoomEventType(value) { | ||
return (value === "open" || | ||
value === "storage" || | ||
return (value === "storage" || | ||
value === "my-presence" || | ||
value === "others-presence" || | ||
value === "event" || | ||
value === "close"); | ||
value === "event"); | ||
} | ||
@@ -700,137 +682,104 @@ function makeIdFactory(connectionId) { | ||
} | ||
function createRoom(name, options) { | ||
const throttleDelay = options.throttle || 100; | ||
const liveblocksServer = options.liveblocksServer || "wss://live.liveblocks.io"; | ||
const authEndpoint = options.authEndpoint; | ||
const _listeners = { | ||
open: [], | ||
storage: [], | ||
event: [], | ||
"others-presence": [], | ||
"my-presence": [], | ||
close: [], | ||
}; | ||
let _idFactory = null; | ||
let _doc = null; | ||
let _storageState = LiveStorageState.NotInitialized; | ||
let _initialStorageFactory = null; | ||
const _state = { | ||
me: null, | ||
socket: null, | ||
lastFlushTime: 0, | ||
flushTimeout: null, | ||
flushData: { | ||
presence: null, | ||
messages: [], | ||
storageOperations: [], | ||
function makeStateMachine(state, context, mockedEffects) { | ||
const effects = mockedEffects || { | ||
authenticate() { | ||
return __awaiter$1(this, void 0, void 0, function* () { | ||
try { | ||
const token = yield auth(context.authEndpoint, context.room); | ||
const connectionId = parseToken(token).actor; | ||
const socket = new WebSocket(`${context.liveblocksServer}/?token=${token}`); | ||
socket.addEventListener("message", onMessage); | ||
socket.addEventListener("open", onOpen); | ||
socket.addEventListener("close", onClose); | ||
socket.addEventListener("error", onError); | ||
authenticationSuccess(connectionId, socket); | ||
} | ||
catch (er) { | ||
authenticationFailure(er); | ||
} | ||
}); | ||
}, | ||
send(messageOrMessages) { | ||
if (state.socket == null) { | ||
throw new Error("Can't send message if socket is null"); | ||
} | ||
state.socket.send(JSON.stringify(messageOrMessages)); | ||
}, | ||
delayFlush(delay) { | ||
return setTimeout(tryFlushing, delay); | ||
}, | ||
startHeartbeatInterval() { | ||
return setInterval(heartbeat, HEARTBEAT_INTERVAL); | ||
}, | ||
schedulePongTimeout() { | ||
return setTimeout(pongTimeout, PONG_TIMEOUT); | ||
}, | ||
scheduleReconnect(delay) { | ||
return setTimeout(connect, delay); | ||
}, | ||
}; | ||
let _users = {}; | ||
let _others = makeOthers(_users); | ||
let state = RoomState.Default; | ||
let numberOfRetry = 0; | ||
let retryTimeoutId = 0; | ||
function send(messageOrMessages) { | ||
if (_state.socket == null) { | ||
throw new Error("Can't send message if socket is null"); | ||
function addEventListener(type, listener) { | ||
if (!isValidRoomEventType(type)) { | ||
throw new Error(`"${type}" is not a valid event name`); | ||
} | ||
_state.socket.send(JSON.stringify(messageOrMessages)); | ||
state.listeners[type].push(listener); | ||
} | ||
function makeId() { | ||
if (_idFactory == null) { | ||
throw new Error("Can't generate id. Id factory is missing."); | ||
function removeEventListener(event, callback) { | ||
if (!isValidRoomEventType(event)) { | ||
throw new Error(`"${event}" is not a valid event name`); | ||
} | ||
return _idFactory(); | ||
const callbacks = state.listeners[event]; | ||
remove(callbacks, callback); | ||
} | ||
function updateUsers(newUsers) { | ||
_users = newUsers; | ||
_others = makeOthers(newUsers); | ||
for (const listener of _listeners["others-presence"]) { | ||
listener(_others); | ||
} | ||
function getConnectionState() { | ||
return state.connection; | ||
} | ||
function dispatch(op) { | ||
_state.flushData.storageOperations.push(op); | ||
tryFlushing(); | ||
function getListenersCount() { | ||
return (state.listeners["my-presence"].length + | ||
state.listeners["others-presence"].length + | ||
state.listeners.storage.length + | ||
state.listeners.event.length); | ||
} | ||
function getStorage() { | ||
if (_storageState === LiveStorageState.Loaded) { | ||
return { | ||
state: _storageState, | ||
root: _doc.root, | ||
}; | ||
function connect() { | ||
if (state.connection.state !== "closed" && | ||
state.connection.state !== "unavailable") { | ||
return null; | ||
} | ||
return { | ||
state: _storageState, | ||
}; | ||
updateConnection({ state: "authenticating" }); | ||
effects.authenticate(); | ||
} | ||
function fetchStorage(initialStorageFactory) { | ||
_initialStorageFactory = initialStorageFactory; | ||
_storageState = LiveStorageState.Loading; | ||
_state.flushData.messages.push({ type: ClientMessageType.FetchStorage }); | ||
tryFlushing(); | ||
} | ||
function updateDoc(doc) { | ||
_doc = doc; | ||
if (doc) { | ||
for (const listener of _listeners.storage) { | ||
listener(getStorage()); | ||
} | ||
function updatePresence(overrides) { | ||
const newPresence = Object.assign(Object.assign({}, state.me), overrides); | ||
if (state.flushData.presence == null) { | ||
state.flushData.presence = overrides; | ||
} | ||
} | ||
function createRecord$1(data) { | ||
return createRecord(makeId(), data); | ||
} | ||
function createList$1() { | ||
return createList(makeId()); | ||
} | ||
function onInitialStorageState(message) { | ||
_storageState = LiveStorageState.Loaded; | ||
if (message.root == null) { | ||
const rootId = makeId(); | ||
_doc = Doc.empty(rootId, (op) => dispatch(op)); | ||
updateDoc(_doc.updateRecord(rootId, _initialStorageFactory({ | ||
createRecord: (data) => createRecord$1(data), | ||
createList: () => createList$1(), | ||
}))); | ||
} | ||
else { | ||
updateDoc(Doc.load(message.root, (op) => dispatch(op))); | ||
for (const key in overrides) { | ||
state.flushData.presence[key] = overrides[key]; | ||
} | ||
} | ||
} | ||
function onStorageUpdates(message) { | ||
if (_doc == null) { | ||
// TODO: Cache updates in case they are coming while root is queried | ||
return; | ||
} | ||
updateDoc(message.ops.reduce((doc, op) => doc.dispatch(op), _doc)); | ||
} | ||
function onOpen() { | ||
state = RoomState.Connected; | ||
numberOfRetry = 0; | ||
state.me = newPresence; | ||
tryFlushing(); | ||
for (const callback of _listeners.open) { | ||
callback(); | ||
for (const listener of state.listeners["my-presence"]) { | ||
listener(state.me); | ||
} | ||
} | ||
function onEvent(message) { | ||
for (const listener of _listeners.event) { | ||
listener({ connectionId: message.actor, event: message.event }); | ||
} | ||
function authenticationSuccess(connectionId, socket) { | ||
updateConnection({ state: "connecting", id: connectionId }); | ||
state.idFactory = makeIdFactory(connectionId); | ||
state.socket = socket; | ||
} | ||
function onRoomStateMessage(message) { | ||
const newUsers = {}; | ||
for (const key in message.users) { | ||
const connectionId = Number.parseInt(key); | ||
const user = message.users[key]; | ||
newUsers[connectionId] = { | ||
connectionId, | ||
info: user.info, | ||
id: user.id, | ||
}; | ||
function authenticationFailure(error) { | ||
console.error(error); | ||
updateConnection({ state: "unavailable" }); | ||
state.numberOfRetry++; | ||
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay()); | ||
} | ||
function onVisibilityChange(visibilityState) { | ||
if (visibilityState === "visible" && state.connection.state === "open") { | ||
heartbeat(); | ||
} | ||
updateUsers(newUsers); | ||
} | ||
function onUpdatePresenceMessage(message) { | ||
const user = _users[message.actor]; | ||
const user = state.users[message.actor]; | ||
const newUser = user | ||
@@ -847,11 +796,41 @@ ? { | ||
}; | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: newUser })); | ||
updateUsers(Object.assign(Object.assign({}, state.users), { [message.actor]: newUser })); | ||
} | ||
function updateUsers(newUsers) { | ||
state.users = newUsers; | ||
state.others = makeOthers(newUsers); | ||
for (const listener of state.listeners["others-presence"]) { | ||
listener(state.others); | ||
} | ||
} | ||
function onUserLeftMessage(message) { | ||
const userLeftMessage = message; | ||
const _a = _users, _b = userLeftMessage.actor; _a[_b]; const rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); | ||
const _a = state.users, _b = userLeftMessage.actor; _a[_b]; const rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); | ||
updateUsers(rest); | ||
} | ||
function onRoomStateMessage(message) { | ||
const newUsers = {}; | ||
for (const key in message.users) { | ||
const connectionId = Number.parseInt(key); | ||
const user = message.users[key]; | ||
newUsers[connectionId] = { | ||
connectionId, | ||
info: user.info, | ||
id: user.id, | ||
}; | ||
} | ||
updateUsers(newUsers); | ||
} | ||
function onNavigatorOnline() { | ||
if (state.connection.state === "unavailable") { | ||
reconnect(); | ||
} | ||
} | ||
function onEvent(message) { | ||
for (const listener of state.listeners.event) { | ||
listener({ connectionId: message.actor, event: message.event }); | ||
} | ||
} | ||
function onUserJoinedMessage(message) { | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: { | ||
updateUsers(Object.assign(Object.assign({}, state.users), { [message.actor]: { | ||
connectionId: message.actor, | ||
@@ -861,12 +840,18 @@ info: message.info, | ||
} })); | ||
// Send current presence to new user | ||
// TODO: Consider storing it on the backend | ||
_state.flushData.messages.push({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: _state.me, | ||
targetActor: message.actor, | ||
}); | ||
tryFlushing(); | ||
if (state.me) { | ||
// Send current presence to new user | ||
// TODO: Consider storing it on the backend | ||
state.flushData.messages.push({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: state.me, | ||
targetActor: message.actor, | ||
}); | ||
tryFlushing(); | ||
} | ||
} | ||
function onMessage(event) { | ||
if (event.data === "pong") { | ||
clearTimeout(state.timeoutHandles.pongTimeout); | ||
return; | ||
} | ||
const message = JSON.parse(event.data); | ||
@@ -904,88 +889,97 @@ switch (message.type) { | ||
} | ||
// function onWakeUp() { | ||
// // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example) | ||
// // The client will not know that the server has probably close the connection even if the readyState is Open | ||
// // One way to detect this kind of pause is to ensure that a setInterval is not taking more than the delay it was configured with | ||
// if (state.connection.state === "open") { | ||
// log("Try to reconnect after laptop wake up"); | ||
// reconnect(); | ||
// } | ||
// } | ||
function onClose(event) { | ||
state.socket = null; | ||
clearTimeout(state.timeoutHandles.pongTimeout); | ||
clearInterval(state.intervalHandles.heartbeat); | ||
if (state.timeoutHandles.flush) { | ||
clearTimeout(state.timeoutHandles.flush); | ||
} | ||
clearTimeout(state.timeoutHandles.reconnect); | ||
updateUsers({}); | ||
if (event.code >= 4000 && event.code <= 4100) { | ||
options.onError(new Error(event.reason)); | ||
updateConnection({ state: "failed" }); | ||
context.onError(new Error(event.reason)); | ||
} | ||
for (const listener of _listeners.close) { | ||
listener(); | ||
else if (event.wasClean === false) { | ||
updateConnection({ state: "unavailable" }); | ||
state.numberOfRetry++; | ||
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay()); | ||
} | ||
_state.socket = null; | ||
updateUsers({}); | ||
if (event.wasClean === false) { | ||
state = RoomState.Default; | ||
numberOfRetry++; | ||
retryTimeoutId = setTimeout(() => connect(), BACKOFF_RETRY_DELAYS[numberOfRetry < BACKOFF_RETRY_DELAYS.length | ||
? numberOfRetry | ||
: BACKOFF_RETRY_DELAYS.length - 1]); | ||
else { | ||
updateConnection({ state: "closed" }); | ||
} | ||
} | ||
function onError(event) { } | ||
function connect() { | ||
return __awaiter$1(this, void 0, void 0, function* () { | ||
if (state === RoomState.Connected || state === RoomState.Connecting) { | ||
return; | ||
} | ||
state = RoomState.Connecting; | ||
let token = null; | ||
let actor = null; | ||
try { | ||
token = yield auth(authEndpoint, name); | ||
actor = parseToken(token).actor; | ||
} | ||
catch (er) { | ||
options.onError(er); | ||
state = RoomState.Error; | ||
return; | ||
} | ||
_idFactory = makeIdFactory(actor); | ||
_state.socket = new WebSocket(`${liveblocksServer}/?token=${token}`); | ||
_state.socket.addEventListener("message", onMessage); | ||
_state.socket.addEventListener("open", onOpen); | ||
_state.socket.addEventListener("close", onClose); | ||
_state.socket.addEventListener("error", onError); | ||
}); | ||
function updateConnection(connection) { | ||
state.connection = connection; | ||
} | ||
function disconnect() { | ||
if (_state.socket) { | ||
_state.socket.removeEventListener("open", onOpen); | ||
_state.socket.removeEventListener("message", onMessage); | ||
_state.socket.removeEventListener("close", onClose); | ||
_state.socket.removeEventListener("error", onError); | ||
_state.socket.close(); | ||
_state.socket = null; | ||
function getRetryDelay() { | ||
return BACKOFF_RETRY_DELAYS[state.numberOfRetry < BACKOFF_RETRY_DELAYS.length | ||
? state.numberOfRetry | ||
: BACKOFF_RETRY_DELAYS.length - 1]; | ||
} | ||
function onError() { } | ||
function onOpen() { | ||
clearInterval(state.intervalHandles.heartbeat); | ||
state.intervalHandles.heartbeat = effects.startHeartbeatInterval(); | ||
updateConnection({ state: "open", id: state.connection.id }); | ||
state.numberOfRetry = 0; | ||
tryFlushing(); | ||
} | ||
function heartbeat() { | ||
if (state.socket == null) { | ||
// Should never happen, because we clear the pong timeout when the connection is dropped explictly | ||
return; | ||
} | ||
state = RoomState.Default; | ||
updateUsers({}); | ||
clearTimeout(retryTimeoutId); | ||
_listeners.open = []; | ||
_listeners["my-presence"] = []; | ||
_listeners["others-presence"] = []; | ||
_listeners.event = []; | ||
_listeners.storage = []; | ||
_listeners.close = []; | ||
clearTimeout(state.timeoutHandles.pongTimeout); | ||
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout(); | ||
if (state.socket.readyState === WebSocket.OPEN) { | ||
state.socket.send("ping"); | ||
} | ||
} | ||
////////////// | ||
// Presence // | ||
////////////// | ||
function getPresence() { | ||
return _state.me; | ||
function pongTimeout() { | ||
reconnect(); | ||
} | ||
function getOthers() { | ||
return _others; | ||
} | ||
function updatePresence(overrides) { | ||
// Create new local presence right away and call listeners | ||
const newPresence = Object.assign(Object.assign({}, _state.me), overrides); | ||
_state.me = newPresence; | ||
for (const listener of _listeners["my-presence"]) { | ||
listener(_state.me); | ||
function reconnect() { | ||
if (state.socket) { | ||
state.socket.removeEventListener("open", onOpen); | ||
state.socket.removeEventListener("message", onMessage); | ||
state.socket.removeEventListener("close", onClose); | ||
state.socket.removeEventListener("error", onError); | ||
state.socket.close(); | ||
state.socket = null; | ||
} | ||
updatePresenceToSend(_state, overrides); | ||
tryFlushing(); | ||
updateConnection({ state: "unavailable" }); | ||
clearTimeout(state.timeoutHandles.pongTimeout); | ||
if (state.timeoutHandles.flush) { | ||
clearTimeout(state.timeoutHandles.flush); | ||
} | ||
clearTimeout(state.timeoutHandles.reconnect); | ||
clearInterval(state.intervalHandles.heartbeat); | ||
connect(); | ||
} | ||
function tryFlushing() { | ||
if (state.socket == null) { | ||
return; | ||
} | ||
if (state.socket.readyState !== WebSocket.OPEN) { | ||
return; | ||
} | ||
const now = Date.now(); | ||
if (canSend(now, _state, throttleDelay)) { | ||
send(flushDataToMessages(_state)); | ||
_state.flushData = { | ||
const elapsedTime = now - state.lastFlushTime; | ||
if (elapsedTime > context.throttleDelay) { | ||
const messages = flushDataToMessages(state); | ||
if (messages.length === 0) { | ||
return; | ||
} | ||
effects.send(messages); | ||
state.flushData = { | ||
messages: [], | ||
@@ -995,28 +989,64 @@ storageOperations: [], | ||
}; | ||
_state.lastFlushTime = Date.now(); | ||
state.lastFlushTime = now; | ||
} | ||
else { | ||
if (_state.flushTimeout) { | ||
clearTimeout(_state.flushTimeout); | ||
_state.flushTimeout = null; | ||
if (state.timeoutHandles.flush != null) { | ||
clearTimeout(state.timeoutHandles.flush); | ||
} | ||
_state.flushTimeout = setTimeout(() => { | ||
if (isSocketReady(_state)) { | ||
flushDataToMessages(_state); | ||
send(flushDataToMessages(_state)); | ||
_state.flushData = { | ||
messages: [], | ||
storageOperations: [], | ||
presence: null, | ||
}; | ||
_state.lastFlushTime = Date.now(); | ||
} | ||
}, throttleDelay - (now - _state.lastFlushTime)); | ||
state.timeoutHandles.flush = effects.delayFlush(context.throttleDelay - (now - state.lastFlushTime)); | ||
} | ||
} | ||
function flushDataToMessages(state) { | ||
const messages = []; | ||
if (state.flushData.presence) { | ||
messages.push({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: state.flushData.presence, | ||
}); | ||
} | ||
for (const event of state.flushData.messages) { | ||
messages.push(event); | ||
} | ||
if (state.flushData.storageOperations.length > 0) { | ||
messages.push({ | ||
type: ClientMessageType.UpdateStorage, | ||
ops: state.flushData.storageOperations, | ||
}); | ||
} | ||
return messages; | ||
} | ||
function disconnect() { | ||
if (state.socket) { | ||
state.socket.removeEventListener("open", onOpen); | ||
state.socket.removeEventListener("message", onMessage); | ||
state.socket.removeEventListener("close", onClose); | ||
state.socket.removeEventListener("error", onError); | ||
state.socket.close(); | ||
state.socket = null; | ||
} | ||
updateConnection({ state: "closed" }); | ||
state.me = null; | ||
if (state.timeoutHandles.flush) { | ||
clearTimeout(state.timeoutHandles.flush); | ||
} | ||
clearTimeout(state.timeoutHandles.reconnect); | ||
clearTimeout(state.timeoutHandles.pongTimeout); | ||
clearInterval(state.intervalHandles.heartbeat); | ||
updateUsers({}); | ||
state.listeners["my-presence"] = []; | ||
state.listeners["others-presence"] = []; | ||
state.listeners.event = []; | ||
state.listeners.storage = []; | ||
} | ||
function getPresence() { | ||
return state.me; | ||
} | ||
function getOthers() { | ||
return state.others; | ||
} | ||
function broadcastEvent(event) { | ||
if (!isSocketReady(_state)) { | ||
if (state.socket == null) { | ||
return; | ||
} | ||
_state.flushData.messages.push({ | ||
state.flushData.messages.push({ | ||
type: ClientMessageType.ClientEvent, | ||
@@ -1027,96 +1057,196 @@ event, | ||
} | ||
function addEventListener(type, listener) { | ||
if (!isValidRoomEventType(type)) { | ||
throw new Error(`"${type}" is not a valid event name`); | ||
/** | ||
* STORAGE | ||
*/ | ||
function onStorageUpdates(message) { | ||
if (state.doc == null) { | ||
// TODO: Cache updates in case they are coming while root is queried | ||
return; | ||
} | ||
_listeners[type].push(listener); | ||
updateDoc(message.ops.reduce((doc, op) => doc.dispatch(op), state.doc)); | ||
} | ||
function removeEventListener(event, callback) { | ||
if (!isValidRoomEventType(event)) { | ||
throw new Error(`"${event}" is not a valid event name`); | ||
function updateDoc(doc) { | ||
state.doc = doc; | ||
if (doc) { | ||
for (const listener of state.listeners.storage) { | ||
listener(getStorage()); | ||
} | ||
} | ||
const callbacks = _listeners[event]; | ||
remove(callbacks, callback); | ||
} | ||
function getStorage() { | ||
if (state.storageState === LiveStorageState.Loaded) { | ||
return { | ||
state: state.storageState, | ||
root: state.doc.root, | ||
}; | ||
} | ||
return { | ||
state: state.storageState, | ||
}; | ||
} | ||
function onInitialStorageState(message) { | ||
state.storageState = LiveStorageState.Loaded; | ||
if (message.root == null) { | ||
const rootId = makeId(); | ||
state.doc = Doc.empty(rootId, (op) => dispatch(op)); | ||
updateDoc(state.doc.updateRecord(rootId, state.initialStorageFactory({ | ||
createRecord: (data) => createRecord$1(data), | ||
createList: () => createList$1(), | ||
}))); | ||
} | ||
else { | ||
updateDoc(Doc.load(message.root, (op) => dispatch(op))); | ||
} | ||
} | ||
function makeId() { | ||
if (state.idFactory == null) { | ||
throw new Error("Can't generate id. Id factory is missing."); | ||
} | ||
return state.idFactory(); | ||
} | ||
function dispatch(op) { | ||
state.flushData.storageOperations.push(op); | ||
tryFlushing(); | ||
} | ||
function createRecord$1(data) { | ||
return createRecord(makeId(), data); | ||
} | ||
function createList$1() { | ||
return createList(makeId()); | ||
} | ||
function fetchStorage(initialStorageFactory) { | ||
state.initialStorageFactory = initialStorageFactory; | ||
state.storageState = LiveStorageState.Loading; | ||
state.flushData.messages.push({ type: ClientMessageType.FetchStorage }); | ||
tryFlushing(); | ||
} | ||
function updateRecord(record, overrides) { | ||
updateDoc(state.doc.updateRecord(record.id, overrides)); | ||
} | ||
function pushItem(list, item) { | ||
updateDoc(state.doc.pushItem(list.id, item)); | ||
} | ||
function deleteItem(list, index) { | ||
updateDoc(state.doc.deleteItem(list.id, index)); | ||
} | ||
function moveItem(list, index, targetIndex) { | ||
updateDoc(state.doc.moveItem(list.id, index, targetIndex)); | ||
} | ||
return { | ||
connect() { | ||
connect(); | ||
}, | ||
// Internal | ||
onOpen, | ||
onClose, | ||
onMessage, | ||
authenticationSuccess, | ||
heartbeat, | ||
onNavigatorOnline, | ||
// onWakeUp, | ||
onVisibilityChange, | ||
// Core | ||
connect, | ||
disconnect, | ||
getListenersCount() { | ||
return (_listeners.open.length + | ||
_listeners["my-presence"].length + | ||
_listeners["others-presence"].length + | ||
_listeners.storage.length + | ||
_listeners.close.length + | ||
_listeners.event.length); | ||
}, | ||
getState() { | ||
return state; | ||
}, | ||
addEventListener, | ||
removeEventListener, | ||
///////////// | ||
// Storage // | ||
///////////// | ||
getStorage, | ||
// Presence | ||
updatePresence, | ||
broadcastEvent, | ||
// Storage | ||
fetchStorage, | ||
createRecord: createRecord$1, | ||
updateRecord, | ||
createList: createList$1, | ||
updateRecord(record, overrides) { | ||
updateDoc(_doc.updateRecord(record.id, overrides)); | ||
pushItem, | ||
deleteItem, | ||
moveItem, | ||
selectors: { | ||
// Core | ||
getListenersCount, | ||
getConnectionState, | ||
// Presence | ||
getPresence, | ||
getOthers, | ||
// Storage | ||
getStorage, | ||
}, | ||
pushItem(list, item) { | ||
updateDoc(_doc.pushItem(list.id, item)); | ||
}; | ||
} | ||
function defaultState() { | ||
return { | ||
connection: { state: "closed" }, | ||
socket: null, | ||
listeners: { | ||
storage: [], | ||
event: [], | ||
"others-presence": [], | ||
"my-presence": [], | ||
}, | ||
deleteItem(list, index) { | ||
updateDoc(_doc.deleteItem(list.id, index)); | ||
numberOfRetry: 0, | ||
lastFlushTime: 0, | ||
timeoutHandles: { | ||
flush: null, | ||
reconnect: 0, | ||
pongTimeout: 0, | ||
}, | ||
moveItem(list, index, targetIndex) { | ||
updateDoc(_doc.moveItem(list.id, index, targetIndex)); | ||
flushData: { | ||
presence: null, | ||
messages: [], | ||
storageOperations: [], | ||
}, | ||
intervalHandles: { | ||
heartbeat: 0, | ||
}, | ||
me: null, | ||
users: {}, | ||
others: makeOthers({}), | ||
storageState: LiveStorageState.NotInitialized, | ||
initialStorageFactory: null, | ||
doc: null, | ||
idFactory: null, | ||
}; | ||
} | ||
function createRoom(name, options) { | ||
const throttleDelay = options.throttle || 100; | ||
const liveblocksServer = options.liveblocksServer || "wss://live.liveblocks.io"; | ||
const authEndpoint = options.authEndpoint; | ||
const state = defaultState(); | ||
const machine = makeStateMachine(state, { | ||
throttleDelay, | ||
liveblocksServer, | ||
authEndpoint, | ||
onError: options.onError, | ||
room: name, | ||
}); | ||
const room = { | ||
///////////// | ||
// Core // | ||
///////////// | ||
connect: machine.connect, | ||
disconnect: machine.disconnect, | ||
getConnectionState: machine.selectors.getConnectionState, | ||
getListenersCount: machine.selectors.getListenersCount, | ||
addEventListener: machine.addEventListener, | ||
removeEventListener: machine.removeEventListener, | ||
///////////// | ||
// Storage // | ||
///////////// | ||
getStorage: machine.selectors.getStorage, | ||
fetchStorage: machine.fetchStorage, | ||
createRecord: machine.createRecord, | ||
createList: machine.createList, | ||
updateRecord: machine.updateRecord, | ||
pushItem: machine.pushItem, | ||
deleteItem: machine.deleteItem, | ||
moveItem: machine.moveItem, | ||
////////////// | ||
// Presence // | ||
////////////// | ||
getPresence, | ||
updatePresence, | ||
getOthers, | ||
broadcastEvent, | ||
getPresence: machine.selectors.getPresence, | ||
updatePresence: machine.updatePresence, | ||
getOthers: machine.selectors.getOthers, | ||
broadcastEvent: machine.broadcastEvent, | ||
}; | ||
room._onNavigatorOnline = machine.onNavigatorOnline; | ||
room._onVisibilityChange = machine.onVisibilityChange; | ||
return room; | ||
} | ||
function flushDataToMessages(state) { | ||
const messages = []; | ||
if (state.flushData.presence) { | ||
messages.push({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: state.flushData.presence, | ||
}); | ||
} | ||
for (const event of state.flushData.messages) { | ||
messages.push(event); | ||
} | ||
if (state.flushData.storageOperations.length > 0) { | ||
messages.push({ | ||
type: ClientMessageType.UpdateStorage, | ||
ops: state.flushData.storageOperations, | ||
}); | ||
} | ||
return messages; | ||
} | ||
function isSocketReady(state) { | ||
return state.socket !== null && state.socket.readyState === WebSocket.OPEN; | ||
} | ||
function canSend(now, state, wait) { | ||
return isSocketReady(state) && now - state.lastFlushTime > wait; | ||
} | ||
function updatePresenceToSend(state, overrides) { | ||
if (state.flushData.presence == null) { | ||
state.flushData.presence = overrides; | ||
} | ||
else { | ||
for (const key in overrides) { | ||
state.flushData.presence[key] = overrides[key]; | ||
} | ||
} | ||
} | ||
@@ -1161,2 +1291,17 @@ function createClient(options) { | ||
} | ||
if (typeof window !== "undefined") { | ||
// TODO: Expose a way to clear these | ||
window.addEventListener("online", () => { | ||
for (const [, room] of rooms) { | ||
room._onNavigatorOnline(); | ||
} | ||
}); | ||
} | ||
if (typeof document !== "undefined") { | ||
document.addEventListener("visibilitychange", () => { | ||
for (const [, room] of rooms) { | ||
room._onVisibilityChange(document.visibilityState); | ||
} | ||
}); | ||
} | ||
return { | ||
@@ -1163,0 +1308,0 @@ addEventListener, |
{ | ||
"name": "@liveblocks/react", | ||
"version": "0.6.0-beta.3", | ||
"version": "0.6.0-beta.4", | ||
"description": "", | ||
@@ -16,3 +16,3 @@ "main": "./lib/index.js", | ||
"dependencies": { | ||
"@liveblocks/client": "0.6.0-beta.3" | ||
"@liveblocks/client": "0.6.0-beta.4" | ||
}, | ||
@@ -19,0 +19,0 @@ "peerDependencies": { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
164306
1511
+ Added@liveblocks/client@0.6.0-beta.4(transitive)
- Removed@liveblocks/client@0.6.0-beta.3(transitive)