@liveblocks/client
Advanced tools
Comparing version 0.6.0-beta.2 to 0.6.0-beta.3
@@ -7,2 +7,7 @@ "use strict"; | ||
function createClient(options) { | ||
if (typeof options.throttle === "number") { | ||
if (options.throttle < 80 || options.throttle > 1000) { | ||
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms"); | ||
} | ||
} | ||
const rooms = new Map(); | ||
@@ -9,0 +14,0 @@ const _listeners = { |
import { Presence } from "./types"; | ||
export declare type ServerMessage = UpdatePresenceMessage | UserJoinMessage | UserLeftMessage | EventMessage | InitialDocumentStateMessage | UpdateStorageMessage; | ||
export declare type ServerMessage = UpdatePresenceMessage | UserJoinMessage | UserLeftMessage | EventMessage | RoomStateMessage | InitialDocumentStateMessage | UpdateStorageMessage; | ||
export declare enum ServerMessageType { | ||
@@ -8,9 +8,19 @@ UpdatePresence = 100, | ||
Event = 103, | ||
RoomState = 104, | ||
InitialStorageState = 200, | ||
UpdateStorage = 201 | ||
} | ||
export declare type RoomStateMessage = { | ||
type: ServerMessageType.RoomState; | ||
users: { | ||
[actor: number]: { | ||
id?: string; | ||
info?: any; | ||
}; | ||
}; | ||
}; | ||
export declare type UpdatePresenceMessage = { | ||
type: ServerMessageType.UpdatePresence; | ||
actor: number; | ||
data: Partial<Presence>; | ||
data: Presence; | ||
}; | ||
@@ -20,2 +30,4 @@ export declare type UserJoinMessage = { | ||
actor: number; | ||
id?: string; | ||
info?: string; | ||
}; | ||
@@ -52,3 +64,3 @@ export declare type UserLeftMessage = { | ||
type: ClientMessageType.UpdatePresence; | ||
data: Partial<Presence>; | ||
data: Presence; | ||
targetActor?: number; | ||
@@ -122,3 +134,8 @@ }; | ||
CLOSE_ABNORMAL = 1006, | ||
INVALID_MESSAGE_FORMAT = 4000 | ||
INVALID_MESSAGE_FORMAT = 4000, | ||
NOT_ALLOWED = 4001, | ||
MAX_NUMBER_OF_MESSAGES_PER_SECONDS = 4002, | ||
MAX_NUMBER_OF_CONCURRENT_CONNECTIONS = 4003, | ||
MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP = 4004, | ||
INTERNAL_ERROR = 4005 | ||
} |
@@ -10,2 +10,3 @@ "use strict"; | ||
ServerMessageType[ServerMessageType["Event"] = 103] = "Event"; | ||
ServerMessageType[ServerMessageType["RoomState"] = 104] = "RoomState"; | ||
ServerMessageType[ServerMessageType["InitialStorageState"] = 200] = "InitialStorageState"; | ||
@@ -39,2 +40,7 @@ ServerMessageType[ServerMessageType["UpdateStorage"] = 201] = "UpdateStorage"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["INVALID_MESSAGE_FORMAT"] = 4000] = "INVALID_MESSAGE_FORMAT"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"; | ||
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 = exports.WebsocketCloseCodes || (exports.WebsocketCloseCodes = {})); |
@@ -61,3 +61,2 @@ "use strict"; | ||
]; | ||
const WAIT = 50; | ||
function isValidRoomEventType(value) { | ||
@@ -76,3 +75,3 @@ return (value === "open" || | ||
function makeOthers(presenceMap) { | ||
const array = Object.entries(presenceMap).map((entry) => [parseInt(entry[0]), entry[1]]); | ||
const array = Object.values(presenceMap); | ||
return { | ||
@@ -91,2 +90,3 @@ get count() { | ||
function createRoom(name, options) { | ||
const throttleDelay = options.throttle || 100; | ||
const liveblocksServer = options.liveblocksServer || "wss://live.liveblocks.io"; | ||
@@ -103,24 +103,26 @@ const authEndpoint = options.authEndpoint; | ||
let _idFactory = null; | ||
let _socket = null; | ||
let _doc = null; | ||
let _storageState = types_1.LiveStorageState.NotInitialized; | ||
let toFlush = []; | ||
let _lastEmit = 0; | ||
let _timeout = null; | ||
let _initialStorageFactory = null; | ||
let _me = null; | ||
const _state = { | ||
me: null, | ||
socket: null, | ||
lastFlushTime: 0, | ||
flushTimeout: null, | ||
flushData: { | ||
presence: null, | ||
messages: [], | ||
storageOperations: [], | ||
}, | ||
}; | ||
let _users = {}; | ||
let _others = makeOthers({}); | ||
let _others = makeOthers(_users); | ||
let state = types_1.RoomState.Default; | ||
let numberOfRetry = 0; | ||
let retryTimeoutId = 0; | ||
let _toSend = null; | ||
let _presenceTimeout = null; | ||
let _lastPresenceEmit = 0; | ||
let onSocketOpenCallbacks = []; | ||
function send(clientMessage) { | ||
if (_socket == null) { | ||
throw new Error("Can't send message if socket is not ready"); | ||
function send(messageOrMessages) { | ||
if (_state.socket == null) { | ||
throw new Error("Can't send message if socket is null"); | ||
} | ||
_socket.send(JSON.stringify(clientMessage)); | ||
_state.socket.send(JSON.stringify(messageOrMessages)); | ||
} | ||
@@ -133,5 +135,5 @@ function makeId() { | ||
} | ||
function updateUsers(users) { | ||
_users = users; | ||
_others = makeOthers(users); | ||
function updateUsers(newUsers) { | ||
_users = newUsers; | ||
_others = makeOthers(newUsers); | ||
for (const listener of _listeners["others-presence"]) { | ||
@@ -142,25 +144,4 @@ listener(_others); | ||
function dispatch(op) { | ||
toFlush.push(op); | ||
const now = Date.now(); | ||
if (now - _lastEmit > WAIT) { | ||
send({ | ||
type: live_1.ClientMessageType.UpdateStorage, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = now; | ||
return; | ||
} | ||
if (_timeout) { | ||
clearTimeout(_timeout); | ||
_timeout = null; | ||
} | ||
_timeout = setTimeout(() => { | ||
send({ | ||
type: live_1.ClientMessageType.UpdateStorage, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = Date.now(); | ||
}, WAIT - (now - _lastEmit)); | ||
_state.flushData.storageOperations.push(op); | ||
tryFlushing(); | ||
} | ||
@@ -181,12 +162,4 @@ function getStorage() { | ||
_storageState = types_1.LiveStorageState.Loading; | ||
if (state === types_1.RoomState.Connected) { | ||
send({ type: live_1.ClientMessageType.FetchStorage }); | ||
} | ||
else { | ||
function onConnect() { | ||
send({ type: live_1.ClientMessageType.FetchStorage }); | ||
utils_1.remove(_listeners.open, onConnect); | ||
} | ||
_listeners.open.push(onConnect); | ||
} | ||
_state.flushData.messages.push({ type: live_1.ClientMessageType.FetchStorage }); | ||
tryFlushing(); | ||
} | ||
@@ -207,8 +180,2 @@ function updateDoc(doc) { | ||
} | ||
function updateMyPresence(me) { | ||
_me = me; | ||
for (const listener of _listeners["my-presence"]) { | ||
listener(_me); | ||
} | ||
} | ||
function onInitialStorageState(message) { | ||
@@ -238,5 +205,3 @@ _storageState = types_1.LiveStorageState.Loaded; | ||
numberOfRetry = 0; | ||
if (_me != null) { | ||
send({ type: live_1.ClientMessageType.UpdatePresence, data: _me }); | ||
} | ||
tryFlushing(); | ||
for (const callback of _listeners.open) { | ||
@@ -251,2 +216,50 @@ callback(); | ||
} | ||
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 onUpdatePresenceMessage(message) { | ||
const user = _users[message.actor]; | ||
const newUser = user | ||
? { | ||
id: user.id, | ||
info: user.info, | ||
connectionId: message.actor, | ||
presence: Object.assign(Object.assign({}, user.presence), message.data), | ||
} | ||
: { | ||
connectionId: message.actor, | ||
presence: message.data, | ||
}; | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: newUser })); | ||
} | ||
function onUserLeftMessage(message) { | ||
const userLeftMessage = message; | ||
const _a = _users, _b = userLeftMessage.actor, notUsed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); | ||
updateUsers(rest); | ||
} | ||
function onUserJoinedMessage(message) { | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: { | ||
connectionId: message.actor, | ||
info: message.info, | ||
id: message.id, | ||
} })); | ||
// Send current presence to new user | ||
// TODO: Consider storing it on the backend | ||
_state.flushData.messages.push({ | ||
type: live_1.ClientMessageType.UpdatePresence, | ||
data: _state.me, | ||
targetActor: message.actor, | ||
}); | ||
tryFlushing(); | ||
} | ||
function onMessage(event) { | ||
@@ -264,14 +277,7 @@ const message = JSON.parse(event.data); | ||
case live_1.ServerMessageType.UserJoined: { | ||
// Send current presence to new user | ||
send({ | ||
type: live_1.ClientMessageType.UpdatePresence, | ||
data: _me, | ||
targetActor: message.actor, | ||
}); | ||
onUserJoinedMessage(message); | ||
break; | ||
} | ||
case live_1.ServerMessageType.UpdatePresence: { | ||
const currentUser = _users[message.actor]; | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: currentUser | ||
? Object.assign(Object.assign({}, currentUser), message.data) : message.data })); | ||
onUpdatePresenceMessage(message); | ||
break; | ||
@@ -284,14 +290,19 @@ } | ||
case live_1.ServerMessageType.UserLeft: { | ||
const userLeftMessage = message; | ||
const _a = _users, _b = userLeftMessage.actor, notUsed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); | ||
updateUsers(rest); | ||
onUserLeftMessage(message); | ||
break; | ||
} | ||
case live_1.ServerMessageType.RoomState: { | ||
onRoomStateMessage(message); | ||
break; | ||
} | ||
} | ||
} | ||
function onClose(event) { | ||
if (event.code >= 4000 && event.code <= 4100) { | ||
options.onError(new Error(event.reason)); | ||
} | ||
for (const listener of _listeners.close) { | ||
listener(); | ||
} | ||
_socket = null; | ||
_state.socket = null; | ||
updateUsers({}); | ||
@@ -325,17 +336,17 @@ if (event.wasClean === false) { | ||
_idFactory = makeIdFactory(actor); | ||
_socket = new WebSocket(`${liveblocksServer}/?token=${token}`); | ||
_socket.addEventListener("message", onMessage); | ||
_socket.addEventListener("open", onOpen); | ||
_socket.addEventListener("close", onClose); | ||
_socket.addEventListener("error", onError); | ||
_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 disconnect() { | ||
if (_socket) { | ||
_socket.removeEventListener("open", onOpen); | ||
_socket.removeEventListener("message", onMessage); | ||
_socket.removeEventListener("close", onClose); | ||
_socket.removeEventListener("error", onError); | ||
_socket.close(); | ||
_socket = null; | ||
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; | ||
} | ||
@@ -356,3 +367,3 @@ state = types_1.RoomState.Default; | ||
function getPresence() { | ||
return _me; | ||
return _state.me; | ||
} | ||
@@ -363,38 +374,50 @@ function getOthers() { | ||
function updatePresence(overrides) { | ||
updateMyPresence(Object.assign(Object.assign({}, _me), overrides)); | ||
if (state !== types_1.RoomState.Connected) { | ||
onSocketOpenCallbacks.push(() => send({ | ||
type: live_1.ClientMessageType.UpdatePresence, | ||
data: _me, | ||
})); | ||
return; | ||
// 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); | ||
} | ||
updatePresenceToSend(_state, overrides); | ||
tryFlushing(); | ||
} | ||
function tryFlushing() { | ||
const now = Date.now(); | ||
if (now - _lastPresenceEmit > WAIT) { | ||
send({ | ||
type: live_1.ClientMessageType.UpdatePresence, | ||
data: Object.assign(Object.assign({}, _toSend), overrides), | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = now; | ||
return; | ||
if (canSend(now, _state, throttleDelay)) { | ||
send(flushDataToMessages(_state)); | ||
_state.flushData = { | ||
messages: [], | ||
storageOperations: [], | ||
presence: null, | ||
}; | ||
_state.lastFlushTime = Date.now(); | ||
} | ||
_toSend = Object.assign(Object.assign({}, _toSend), overrides); | ||
if (_presenceTimeout) { | ||
clearTimeout(_presenceTimeout); | ||
_presenceTimeout = null; | ||
else { | ||
if (_state.flushTimeout) { | ||
clearTimeout(_state.flushTimeout); | ||
_state.flushTimeout = null; | ||
} | ||
_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)); | ||
} | ||
_presenceTimeout = setTimeout(() => { | ||
send({ | ||
type: live_1.ClientMessageType.UpdatePresence, | ||
data: _toSend, | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = Date.now(); | ||
}, WAIT - (now - _lastPresenceEmit)); | ||
} | ||
function broadcastEvent(event) { | ||
if (state === types_1.RoomState.Connected) { | ||
send({ type: live_1.ClientMessageType.ClientEvent, event }); | ||
if (!isSocketReady(_state)) { | ||
return; | ||
} | ||
_state.flushData.messages.push({ | ||
type: live_1.ClientMessageType.ClientEvent, | ||
event, | ||
}); | ||
tryFlushing(); | ||
} | ||
@@ -461,1 +484,36 @@ function addEventListener(type, listener) { | ||
exports.createRoom = createRoom; | ||
function flushDataToMessages(state) { | ||
const messages = []; | ||
if (state.flushData.presence) { | ||
messages.push({ | ||
type: live_1.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: live_1.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]; | ||
} | ||
} | ||
} |
import { RecordData, Record, List } from "./doc"; | ||
export declare type User<T extends Presence = Presence> = { | ||
connectionId: number; | ||
id?: string; | ||
info?: any; | ||
presence?: T; | ||
}; | ||
/** | ||
@@ -7,4 +13,4 @@ * Public | ||
readonly count: number; | ||
toArray(): Array<[connectionId: number, presence: T | undefined]>; | ||
map<U>(callback: (entry: [connectionId: number, presence: T | undefined]) => U): Array<U>; | ||
toArray(): Array<User<T>>; | ||
map<U>(callback: (user: User<T>) => U): Array<U>; | ||
} | ||
@@ -21,2 +27,3 @@ export declare type Presence = Serializable; | ||
liveblocksServer?: string; | ||
throttle?: number; | ||
}; | ||
@@ -23,0 +30,0 @@ export declare type AuthorizeResponse = { |
import { createRoom } from "./room"; | ||
import { remove } from "./utils"; | ||
export function createClient(options) { | ||
if (typeof options.throttle === "number") { | ||
if (options.throttle < 80 || options.throttle > 1000) { | ||
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms"); | ||
} | ||
} | ||
const rooms = new Map(); | ||
@@ -5,0 +10,0 @@ const _listeners = { |
import { Presence } from "./types"; | ||
export declare type ServerMessage = UpdatePresenceMessage | UserJoinMessage | UserLeftMessage | EventMessage | InitialDocumentStateMessage | UpdateStorageMessage; | ||
export declare type ServerMessage = UpdatePresenceMessage | UserJoinMessage | UserLeftMessage | EventMessage | RoomStateMessage | InitialDocumentStateMessage | UpdateStorageMessage; | ||
export declare enum ServerMessageType { | ||
@@ -8,9 +8,19 @@ UpdatePresence = 100, | ||
Event = 103, | ||
RoomState = 104, | ||
InitialStorageState = 200, | ||
UpdateStorage = 201 | ||
} | ||
export declare type RoomStateMessage = { | ||
type: ServerMessageType.RoomState; | ||
users: { | ||
[actor: number]: { | ||
id?: string; | ||
info?: any; | ||
}; | ||
}; | ||
}; | ||
export declare type UpdatePresenceMessage = { | ||
type: ServerMessageType.UpdatePresence; | ||
actor: number; | ||
data: Partial<Presence>; | ||
data: Presence; | ||
}; | ||
@@ -20,2 +30,4 @@ export declare type UserJoinMessage = { | ||
actor: number; | ||
id?: string; | ||
info?: string; | ||
}; | ||
@@ -52,3 +64,3 @@ export declare type UserLeftMessage = { | ||
type: ClientMessageType.UpdatePresence; | ||
data: Partial<Presence>; | ||
data: Presence; | ||
targetActor?: number; | ||
@@ -122,3 +134,8 @@ }; | ||
CLOSE_ABNORMAL = 1006, | ||
INVALID_MESSAGE_FORMAT = 4000 | ||
INVALID_MESSAGE_FORMAT = 4000, | ||
NOT_ALLOWED = 4001, | ||
MAX_NUMBER_OF_MESSAGES_PER_SECONDS = 4002, | ||
MAX_NUMBER_OF_CONCURRENT_CONNECTIONS = 4003, | ||
MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP = 4004, | ||
INTERNAL_ERROR = 4005 | ||
} |
@@ -7,2 +7,3 @@ export var ServerMessageType; | ||
ServerMessageType[ServerMessageType["Event"] = 103] = "Event"; | ||
ServerMessageType[ServerMessageType["RoomState"] = 104] = "RoomState"; | ||
ServerMessageType[ServerMessageType["InitialStorageState"] = 200] = "InitialStorageState"; | ||
@@ -36,2 +37,7 @@ ServerMessageType[ServerMessageType["UpdateStorage"] = 201] = "UpdateStorage"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["INVALID_MESSAGE_FORMAT"] = 4000] = "INVALID_MESSAGE_FORMAT"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS"; | ||
WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"; | ||
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 = {})); |
@@ -39,3 +39,2 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
]; | ||
const WAIT = 50; | ||
function isValidRoomEventType(value) { | ||
@@ -54,3 +53,3 @@ return (value === "open" || | ||
function makeOthers(presenceMap) { | ||
const array = Object.entries(presenceMap).map((entry) => [parseInt(entry[0]), entry[1]]); | ||
const array = Object.values(presenceMap); | ||
return { | ||
@@ -69,2 +68,3 @@ get count() { | ||
export function createRoom(name, options) { | ||
const throttleDelay = options.throttle || 100; | ||
const liveblocksServer = options.liveblocksServer || "wss://live.liveblocks.io"; | ||
@@ -81,24 +81,26 @@ const authEndpoint = options.authEndpoint; | ||
let _idFactory = null; | ||
let _socket = null; | ||
let _doc = null; | ||
let _storageState = LiveStorageState.NotInitialized; | ||
let toFlush = []; | ||
let _lastEmit = 0; | ||
let _timeout = null; | ||
let _initialStorageFactory = null; | ||
let _me = null; | ||
const _state = { | ||
me: null, | ||
socket: null, | ||
lastFlushTime: 0, | ||
flushTimeout: null, | ||
flushData: { | ||
presence: null, | ||
messages: [], | ||
storageOperations: [], | ||
}, | ||
}; | ||
let _users = {}; | ||
let _others = makeOthers({}); | ||
let _others = makeOthers(_users); | ||
let state = RoomState.Default; | ||
let numberOfRetry = 0; | ||
let retryTimeoutId = 0; | ||
let _toSend = null; | ||
let _presenceTimeout = null; | ||
let _lastPresenceEmit = 0; | ||
let onSocketOpenCallbacks = []; | ||
function send(clientMessage) { | ||
if (_socket == null) { | ||
throw new Error("Can't send message if socket is not ready"); | ||
function send(messageOrMessages) { | ||
if (_state.socket == null) { | ||
throw new Error("Can't send message if socket is null"); | ||
} | ||
_socket.send(JSON.stringify(clientMessage)); | ||
_state.socket.send(JSON.stringify(messageOrMessages)); | ||
} | ||
@@ -111,5 +113,5 @@ function makeId() { | ||
} | ||
function updateUsers(users) { | ||
_users = users; | ||
_others = makeOthers(users); | ||
function updateUsers(newUsers) { | ||
_users = newUsers; | ||
_others = makeOthers(newUsers); | ||
for (const listener of _listeners["others-presence"]) { | ||
@@ -120,25 +122,4 @@ listener(_others); | ||
function dispatch(op) { | ||
toFlush.push(op); | ||
const now = Date.now(); | ||
if (now - _lastEmit > WAIT) { | ||
send({ | ||
type: ClientMessageType.UpdateStorage, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = now; | ||
return; | ||
} | ||
if (_timeout) { | ||
clearTimeout(_timeout); | ||
_timeout = null; | ||
} | ||
_timeout = setTimeout(() => { | ||
send({ | ||
type: ClientMessageType.UpdateStorage, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = Date.now(); | ||
}, WAIT - (now - _lastEmit)); | ||
_state.flushData.storageOperations.push(op); | ||
tryFlushing(); | ||
} | ||
@@ -159,12 +140,4 @@ function getStorage() { | ||
_storageState = LiveStorageState.Loading; | ||
if (state === RoomState.Connected) { | ||
send({ type: ClientMessageType.FetchStorage }); | ||
} | ||
else { | ||
function onConnect() { | ||
send({ type: ClientMessageType.FetchStorage }); | ||
remove(_listeners.open, onConnect); | ||
} | ||
_listeners.open.push(onConnect); | ||
} | ||
_state.flushData.messages.push({ type: ClientMessageType.FetchStorage }); | ||
tryFlushing(); | ||
} | ||
@@ -185,8 +158,2 @@ function updateDoc(doc) { | ||
} | ||
function updateMyPresence(me) { | ||
_me = me; | ||
for (const listener of _listeners["my-presence"]) { | ||
listener(_me); | ||
} | ||
} | ||
function onInitialStorageState(message) { | ||
@@ -216,5 +183,3 @@ _storageState = LiveStorageState.Loaded; | ||
numberOfRetry = 0; | ||
if (_me != null) { | ||
send({ type: ClientMessageType.UpdatePresence, data: _me }); | ||
} | ||
tryFlushing(); | ||
for (const callback of _listeners.open) { | ||
@@ -229,2 +194,50 @@ callback(); | ||
} | ||
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 onUpdatePresenceMessage(message) { | ||
const user = _users[message.actor]; | ||
const newUser = user | ||
? { | ||
id: user.id, | ||
info: user.info, | ||
connectionId: message.actor, | ||
presence: Object.assign(Object.assign({}, user.presence), message.data), | ||
} | ||
: { | ||
connectionId: message.actor, | ||
presence: message.data, | ||
}; | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: newUser })); | ||
} | ||
function onUserLeftMessage(message) { | ||
const userLeftMessage = message; | ||
const _a = _users, _b = userLeftMessage.actor, notUsed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); | ||
updateUsers(rest); | ||
} | ||
function onUserJoinedMessage(message) { | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: { | ||
connectionId: message.actor, | ||
info: message.info, | ||
id: message.id, | ||
} })); | ||
// 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) { | ||
@@ -242,14 +255,7 @@ const message = JSON.parse(event.data); | ||
case ServerMessageType.UserJoined: { | ||
// Send current presence to new user | ||
send({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: _me, | ||
targetActor: message.actor, | ||
}); | ||
onUserJoinedMessage(message); | ||
break; | ||
} | ||
case ServerMessageType.UpdatePresence: { | ||
const currentUser = _users[message.actor]; | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: currentUser | ||
? Object.assign(Object.assign({}, currentUser), message.data) : message.data })); | ||
onUpdatePresenceMessage(message); | ||
break; | ||
@@ -262,14 +268,19 @@ } | ||
case ServerMessageType.UserLeft: { | ||
const userLeftMessage = message; | ||
const _a = _users, _b = userLeftMessage.actor, notUsed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); | ||
updateUsers(rest); | ||
onUserLeftMessage(message); | ||
break; | ||
} | ||
case ServerMessageType.RoomState: { | ||
onRoomStateMessage(message); | ||
break; | ||
} | ||
} | ||
} | ||
function onClose(event) { | ||
if (event.code >= 4000 && event.code <= 4100) { | ||
options.onError(new Error(event.reason)); | ||
} | ||
for (const listener of _listeners.close) { | ||
listener(); | ||
} | ||
_socket = null; | ||
_state.socket = null; | ||
updateUsers({}); | ||
@@ -303,17 +314,17 @@ if (event.wasClean === false) { | ||
_idFactory = makeIdFactory(actor); | ||
_socket = new WebSocket(`${liveblocksServer}/?token=${token}`); | ||
_socket.addEventListener("message", onMessage); | ||
_socket.addEventListener("open", onOpen); | ||
_socket.addEventListener("close", onClose); | ||
_socket.addEventListener("error", onError); | ||
_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 disconnect() { | ||
if (_socket) { | ||
_socket.removeEventListener("open", onOpen); | ||
_socket.removeEventListener("message", onMessage); | ||
_socket.removeEventListener("close", onClose); | ||
_socket.removeEventListener("error", onError); | ||
_socket.close(); | ||
_socket = null; | ||
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; | ||
} | ||
@@ -334,3 +345,3 @@ state = RoomState.Default; | ||
function getPresence() { | ||
return _me; | ||
return _state.me; | ||
} | ||
@@ -341,38 +352,50 @@ function getOthers() { | ||
function updatePresence(overrides) { | ||
updateMyPresence(Object.assign(Object.assign({}, _me), overrides)); | ||
if (state !== RoomState.Connected) { | ||
onSocketOpenCallbacks.push(() => send({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: _me, | ||
})); | ||
return; | ||
// 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); | ||
} | ||
updatePresenceToSend(_state, overrides); | ||
tryFlushing(); | ||
} | ||
function tryFlushing() { | ||
const now = Date.now(); | ||
if (now - _lastPresenceEmit > WAIT) { | ||
send({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: Object.assign(Object.assign({}, _toSend), overrides), | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = now; | ||
return; | ||
if (canSend(now, _state, throttleDelay)) { | ||
send(flushDataToMessages(_state)); | ||
_state.flushData = { | ||
messages: [], | ||
storageOperations: [], | ||
presence: null, | ||
}; | ||
_state.lastFlushTime = Date.now(); | ||
} | ||
_toSend = Object.assign(Object.assign({}, _toSend), overrides); | ||
if (_presenceTimeout) { | ||
clearTimeout(_presenceTimeout); | ||
_presenceTimeout = null; | ||
else { | ||
if (_state.flushTimeout) { | ||
clearTimeout(_state.flushTimeout); | ||
_state.flushTimeout = null; | ||
} | ||
_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)); | ||
} | ||
_presenceTimeout = setTimeout(() => { | ||
send({ | ||
type: ClientMessageType.UpdatePresence, | ||
data: _toSend, | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = Date.now(); | ||
}, WAIT - (now - _lastPresenceEmit)); | ||
} | ||
function broadcastEvent(event) { | ||
if (state === RoomState.Connected) { | ||
send({ type: ClientMessageType.ClientEvent, event }); | ||
if (!isSocketReady(_state)) { | ||
return; | ||
} | ||
_state.flushData.messages.push({ | ||
type: ClientMessageType.ClientEvent, | ||
event, | ||
}); | ||
tryFlushing(); | ||
} | ||
@@ -438,1 +461,36 @@ function addEventListener(type, listener) { | ||
} | ||
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]; | ||
} | ||
} | ||
} |
import { RecordData, Record, List } from "./doc"; | ||
export declare type User<T extends Presence = Presence> = { | ||
connectionId: number; | ||
id?: string; | ||
info?: any; | ||
presence?: T; | ||
}; | ||
/** | ||
@@ -7,4 +13,4 @@ * Public | ||
readonly count: number; | ||
toArray(): Array<[connectionId: number, presence: T | undefined]>; | ||
map<U>(callback: (entry: [connectionId: number, presence: T | undefined]) => U): Array<U>; | ||
toArray(): Array<User<T>>; | ||
map<U>(callback: (user: User<T>) => U): Array<U>; | ||
} | ||
@@ -21,2 +27,3 @@ export declare type Presence = Serializable; | ||
liveblocksServer?: string; | ||
throttle?: number; | ||
}; | ||
@@ -23,0 +30,0 @@ export declare type AuthorizeResponse = { |
{ | ||
"name": "@liveblocks/client", | ||
"version": "0.6.0-beta.2", | ||
"version": "0.6.0-beta.3", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "./lib/cjs/index.js", |
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
106087
3042