New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@liveblocks/react

Package Overview
Dependencies
Maintainers
2
Versions
461
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@liveblocks/react - npm Package Compare versions

Comparing version 0.9.0 to 0.10.0

3

lib/index.d.ts

@@ -152,3 +152,2 @@ import { Client, RecordData, Others, Presence, Record, InitialStorageFactory, Room, User } from "@liveblocks/client";

export declare function useStorageActions(): StorageActions;
export { createClient } from "@liveblocks/client";
export type { Record, Client, List } from "@liveblocks/client";
export {};

@@ -47,559 +47,2 @@ Object.defineProperty(exports, '__esModule', { value: true });

const min = 32;
const max = 127;
function makePosition(before, after) {
// No children
if (before == null && after == null) {
return pos([min + 1]);
}
// Insert at the end
if (before != null && after == null) {
return getNextPosition(before);
}
// Insert at the start
if (before == null && after != null) {
return getPreviousPosition(after);
}
return pos(makePositionFromCodes(posCodes(before), posCodes(after)));
}
function getPreviousPosition(after) {
const result = [];
const afterCodes = posCodes(after);
for (let i = 0; i < afterCodes.length; i++) {
const code = afterCodes[i];
if (code <= min + 1) {
result.push(min);
if (afterCodes.length - 1 === i) {
result.push(max - 1);
break;
}
}
else {
result.push(code - 1);
break;
}
}
return pos(result);
}
function getNextPosition(before) {
const result = [];
const beforeCodes = posCodes(before);
for (let i = 0; i < beforeCodes.length; i++) {
const code = beforeCodes[i];
if (code === max - 1) {
result.push(code);
if (beforeCodes.length - 1 === i) {
result.push(min + 1);
break;
}
}
else {
result.push(code + 1);
break;
}
}
return pos(result);
}
function makePositionFromCodes(before, after) {
let index = 0;
const result = [];
while (true) {
const beforeDigit = before[index] || min;
const afterDigit = after[index] || max;
if (beforeDigit > afterDigit) {
throw new Error(`Impossible to generate position between ${before} and ${after}`);
}
if (beforeDigit === afterDigit) {
result.push(beforeDigit);
index++;
continue;
}
if (afterDigit - beforeDigit === 1) {
result.push(beforeDigit);
result.push(...makePositionFromCodes(before.slice(index + 1), []));
break;
}
const mid = beforeDigit + Math.floor((afterDigit - beforeDigit) / 2);
result.push(mid);
break;
}
return result;
}
function posCodes(str) {
const codes = [];
for (let i = 0; i < str.length; i++) {
codes.push(str.charCodeAt(i));
}
return codes;
}
function pos(codes) {
return String.fromCharCode(...codes);
}
function compare(itemA, itemB) {
const aCodes = posCodes(itemA.position);
const bCodes = posCodes(itemB.position);
const maxLength = Math.max(aCodes.length, bCodes.length);
for (let i = 0; i < maxLength; i++) {
const a = aCodes[i] == null ? min : aCodes[i];
const b = bCodes[i] == null ? min : bCodes[i];
if (a === b) {
continue;
}
else {
return a - b;
}
}
throw new Error(`Impossible to compare similar position "${itemA.position}" and "${itemB.position}"`);
}
const RECORD = Symbol("liveblocks.record");
const LIST = Symbol("liveblocks.list");
function createRecord(id, data) {
return Object.assign({ id, $$type: RECORD }, data);
}
function createList(id, items = []) {
return {
id,
$$type: LIST,
length: items.length,
toArray: () => items,
map: (callback) => items.map(callback),
};
}
function noop() { }
class Doc {
constructor(root, _cache, _emit) {
this.root = root;
this._cache = _cache;
this._emit = _emit;
}
static empty(id = "root", emit = noop) {
const root = {
id,
$$type: RECORD,
};
return new Doc(root, { links: new Map(), listCache: new Map() }, emit);
}
static createFromRoot(data, id = "root", emit = noop) {
let doc = Doc.empty(id, emit);
doc = doc.updateRecord(doc.root.id, data);
return doc;
}
static load(root, emit = noop) {
let doc = Doc.empty(root.id, emit);
return doc.dispatch({
type: OpType.RecordUpdate,
id: root.id,
data: root.data,
});
}
get data() {
return this.root;
}
dispatch(op, shouldEmit = false) {
if (shouldEmit) {
this._emit(op);
}
if (op.id === this.root.id) {
const node = dispatch(this.root, op, this._cache, []);
return new Doc(node, this._cache, this._emit);
}
else {
const links = getAllLinks(op.id, this.root.id, this._cache.links);
const node = dispatch(this.root, op, this._cache, links);
return new Doc(node, this._cache, this._emit);
}
}
getChild(id) {
if (id === this.root.id) {
return this.root;
}
const allLinks = getAllLinks(id, this.root.id, this._cache.links);
return getChildDeep(this.root, id, allLinks, this._cache);
}
updateRecord(id, overrides) {
const currentRecord = this.getChild(id);
if (currentRecord == null) {
throw new Error(`Record with id "${id}" does not exist`);
}
let data = {};
for (const key in overrides) {
const value = overrides[key];
data[key] = serialize(value);
}
const op = {
id: currentRecord.id,
type: OpType.RecordUpdate,
data,
};
return this.dispatch(op, true);
}
pushItem(id, item) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
}
if (!isRecord(item)) {
throw new Error("List can't only have Record as children");
}
const data = serialize(item);
if (list.length === 0) {
return this.dispatch({
type: OpType.ListInsert,
id: list.id,
position: makePosition(),
data,
}, true);
}
const items = sortedListItems(getListItems(this._cache, id));
const [tailPosition] = items[items.length - 1];
const position = makePosition(tailPosition);
const operation = {
type: OpType.ListInsert,
id: list.id,
position,
data,
};
return this.dispatch(operation, true);
}
moveItem(id, index, targetIndex) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
}
const items = sortedListItems(getListItems(this._cache, id));
if (targetIndex < 0) {
throw new Error("targetIndex cannot be less than 0");
}
if (targetIndex >= items.length) {
throw new Error("targetIndex cannot be greater or equal than the list length");
}
if (index < 0) {
throw new Error("index cannot be less than 0");
}
if (index >= items.length) {
throw new Error("index cannot be greater or equal than the list length");
}
if (index === targetIndex) {
return this;
}
let beforePosition = null;
let afterPosition = null;
if (index < targetIndex) {
afterPosition =
targetIndex === items.length - 1
? undefined
: items[targetIndex + 1][0];
beforePosition = items[targetIndex][0];
}
else {
afterPosition = items[targetIndex][0];
beforePosition =
targetIndex === 0 ? undefined : items[targetIndex - 1][0];
}
const position = makePosition(beforePosition, afterPosition);
const [, item] = items[index];
return this.dispatch({
type: OpType.ListMove,
id: list.id,
itemId: item.id,
position,
}, true);
}
deleteItem(id, index) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
}
const items = sortedListItems(getListItems(this._cache, id));
const [, item] = items[index];
return this.dispatch({
type: OpType.ListRemove,
id: list.id,
itemId: item.id,
}, true);
}
deleteItemById(id, itemId) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
}
const itemsMap = getListItems(this._cache, id);
let item = null;
for (const [, crdt] of itemsMap) {
if (crdt.id === itemId) {
item = crdt;
break;
}
}
if (item == null) {
throw new Error(`List with id "${id}" does not have an item with id "${itemId}"`);
}
return this.dispatch({
type: OpType.ListRemove,
id: list.id,
itemId: item.id,
}, true);
}
}
function getAllLinks(id, rootId, links) {
let currentId = id;
const result = [];
do {
const link = links.get(currentId);
if (link == null) {
throw new Error(`Can't find link for id "${currentId}"`);
}
currentId = link.parentId;
result.push(link);
} while (currentId !== rootId);
return result;
}
function deserializeList(serialized, cache) {
const listItems = new Map();
for (const position in serialized.data) {
const item = deserialize(serialized.data[position], cache);
if (!isRecord(item)) {
throw new Error("TODO");
}
listItems.set(position, item);
cache.links.set(item.id, { parentId: serialized.id, parentKey: position });
}
cache.listCache.set(serialized.id, listItems);
return createList(serialized.id, listItemsToArray(listItems));
}
function getListItems(cache, listId) {
const items = cache.listCache.get(listId);
if (items == null) {
throw new Error(`Can't find list cache for id "${listId}"`);
}
return items;
}
function deserializeRecord(serialized, cache) {
const result = {
id: serialized.id,
$$type: RECORD,
};
for (const key in serialized.data) {
const item = deserialize(serialized.data[key], cache);
if (isCrdt(item)) {
cache.links.set(item.id, {
parentId: serialized.id,
parentKey: key,
});
}
result[key] = item;
}
return result;
}
function deserialize(serialized, cache) {
switch (serialized.type) {
case CrdtType.Register: {
return serialized.data;
}
case CrdtType.Record: {
return deserializeRecord(serialized, cache);
}
case CrdtType.List: {
return deserializeList(serialized, cache);
}
default: {
throw new Error("TODO");
}
}
}
function dispatchOnRecord(record, op, cache, links) {
if (links.length === 0) {
if (record.id !== op.id) {
throw new Error("TODO");
}
switch (op.type) {
case OpType.RecordUpdate: {
return updateRecord(record, op, cache);
}
default: {
console.warn("Unsupported operation");
return record;
}
}
}
const currentLink = links.pop();
const child = record[currentLink.parentKey];
const newNode = dispatch(child, op, cache, links);
return Object.assign(Object.assign({}, record), { [currentLink.parentKey]: newNode });
}
function dispatchOnList(list, op, cache, links) {
if (links.length === 0) {
if (list.id !== op.id) {
throw new Error("TODO");
}
switch (op.type) {
case OpType.ListInsert: {
return listInsert(list, op, cache);
}
case OpType.ListMove: {
return listMove(list, op, cache);
}
case OpType.ListRemove: {
return listDelete(list, op, cache);
}
default: {
console.warn("Unsupported operation");
return list;
}
}
}
const currentLink = links.pop();
const position = currentLink.parentKey;
const items = getListItems(cache, list.id);
const item = items.get(position);
if (item == null) {
throw new Error("TODO");
}
const newItem = dispatch(item, op, cache, links);
items.set(position, newItem);
return createList(list.id, listItemsToArray(items));
}
function dispatch(node, op, cache, links) {
switch (node.$$type) {
case RECORD:
return dispatchOnRecord(node, op, cache, links);
case LIST:
return dispatchOnList(node, op, cache, links);
default: {
throw new Error("Unknown CRDT");
}
}
}
function updateRecord(node, op, cache) {
const result = Object.assign({}, node);
for (const key in op.data) {
const value = op.data[key];
const item = deserialize(value, cache);
if (isCrdt(item)) {
cache.links.set(item.id, { parentId: node.id, parentKey: key });
}
result[key] = item;
}
return result;
}
function listInsert(list, op, cache) {
const items = getListItems(cache, list.id);
const item = deserialize(op.data, cache);
if (isCrdt(item)) {
items.set(op.position, item);
cache.links.set(item.id, { parentId: list.id, parentKey: op.position });
}
return createList(list.id, listItemsToArray(items));
}
function listMove(list, op, cache) {
const items = getListItems(cache, list.id);
const link = getLinkOrThrow(cache, op.itemId);
const item = items.get(link.parentKey);
if (item == null) {
throw new Error("TODO");
}
// Delete old position cache entry
items.delete(link.parentKey);
// Insert new position in cache
items.set(op.position, item);
// Update link
cache.links.set(op.itemId, { parentId: list.id, parentKey: op.position });
return createList(list.id, listItemsToArray(items));
}
function getLinkOrThrow(cache, id) {
const link = cache.links.get(id);
if (link == null) {
throw new Error(`Can't find link with id "${id}"`);
}
return link;
}
function listDelete(list, op, cache) {
const items = getListItems(cache, list.id);
const link = getLinkOrThrow(cache, op.itemId);
items.delete(link.parentKey);
cache.links.delete(op.itemId);
return createList(list.id, listItemsToArray(items));
}
function listItemsToArray(items) {
return sortedListItems(items).map((entry) => entry[1]);
}
function sortedListItems(items) {
return Array.from(items.entries()).sort((entryA, entryB) => compare({ position: entryA[0] }, { position: entryB[0] }));
}
function getChildDeep(node, id, links, cache) {
let currentNode = node;
while (currentNode.id !== id) {
const link = links.pop();
if (link == null || link.parentId !== currentNode.id) {
throw new Error("TODO");
}
if (currentNode.$$type === RECORD) {
currentNode = currentNode[link.parentKey];
}
else {
const listItems = getListItems(cache, currentNode.id);
const item = listItems.get(link.parentKey);
if (item == null) {
throw new Error("TODO");
}
currentNode = item;
}
}
return currentNode;
}
function isRecord(value) {
return value != null && typeof value === "object" && value.$$type === RECORD;
}
function isList(value) {
return value != null && typeof value === "object" && value.$$type === LIST;
}
function isCrdt(value) {
return isRecord(value) || isList(value);
}
function serializeRecord(record) {
const serializedData = {};
for (const key in record) {
if (key !== "id" && key !== "$$type") {
const value = record[key]; // TODO: Find out why typescript does not like that
serializedData[key] = serialize(value);
}
}
return {
id: record.id,
type: CrdtType.Record,
data: serializedData,
};
}
function serializeList(list) {
return {
id: list.id,
type: CrdtType.List,
data: {},
};
}
function serialize(value) {
if (isRecord(value)) {
return serializeRecord(value);
}
else if (isList(value)) {
return serializeList(value);
}
else {
return { type: CrdtType.Register, data: value };
}
}
var LiveStorageState;

@@ -612,12 +55,3 @@ (function (LiveStorageState) {

function remove(array, item) {
for (let i = 0; i < array.length; i++) {
if (array[i] === item) {
array.splice(i, 1);
break;
}
}
}
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
(undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }

@@ -631,58 +65,4 @@ return new (P || (P = Promise))(function (resolve, reject) {

};
function fetchAuthorize(endpoint, room) {
return __awaiter(this, void 0, void 0, function* () {
const res = yield fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
room,
}),
});
if (!res.ok) {
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
}
let authResponse = null;
try {
authResponse = yield res.json();
}
catch (er) {
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
}
if (typeof authResponse.token !== "string") {
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
}
return authResponse.token;
});
}
function auth(endpoint, room) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof endpoint === "string") {
return fetchAuthorize(endpoint, room);
}
if (typeof endpoint === "function") {
return endpoint(room);
}
throw new Error("Authentication error. Liveblocks could not parse the response of your authentication endpoint");
});
}
class AuthenticationError extends Error {
constructor(message) {
super(message);
}
}
function parseToken(token) {
const tokenParts = token.split(".");
if (tokenParts.length !== 3) {
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
}
const data = JSON.parse(atob(tokenParts[1]));
if (typeof data.actor !== "number") {
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
}
return data;
}
var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
(undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }

@@ -696,706 +76,3 @@ return new (P || (P = Promise))(function (resolve, reject) {

};
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 === "storage" ||
value === "my-presence" ||
value === "others" ||
value === "event" ||
value === "error" ||
value === "connection");
}
function makeIdFactory(connectionId) {
let count = 0;
return () => `${connectionId}:${count++}`;
}
function makeOthers(presenceMap) {
const array = Object.values(presenceMap);
return {
get count() {
return array.length;
},
map(callback) {
return array.map(callback);
},
toArray() {
return array;
},
};
}
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 parsedToken = parseToken(token);
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(parsedToken, 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);
},
};
function subscribe(type, listener) {
if (!isValidRoomEventType(type)) {
throw new Error(`"${type}" is not a valid event name`);
}
state.listeners[type].push(listener);
}
function unsubscribe(event, callback) {
if (!isValidRoomEventType(event)) {
throw new Error(`"${event}" is not a valid event name`);
}
const callbacks = state.listeners[event];
remove(callbacks, callback);
}
function getConnectionState() {
return state.connection.state;
}
function getCurrentUser() {
return state.connection.state === "open" ||
state.connection.state === "connecting"
? {
connectionId: state.connection.id,
id: state.connection.userId,
info: state.connection.userInfo,
presence: getPresence(),
}
: null;
}
function connect() {
if (typeof window === "undefined") {
return;
}
if (state.connection.state !== "closed" &&
state.connection.state !== "unavailable") {
return null;
}
updateConnection({ state: "authenticating" });
effects.authenticate();
}
function updatePresence(overrides) {
const newPresence = Object.assign(Object.assign({}, state.me), overrides);
if (state.flushData.presence == null) {
state.flushData.presence = overrides;
}
else {
for (const key in overrides) {
state.flushData.presence[key] = overrides[key];
}
}
state.me = newPresence;
tryFlushing();
for (const listener of state.listeners["my-presence"]) {
listener(state.me);
}
}
function authenticationSuccess(token, socket) {
updateConnection({
state: "connecting",
id: token.actor,
userInfo: token.info,
userId: token.id,
});
state.idFactory = makeIdFactory(token.actor);
state.socket = socket;
}
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();
}
}
function onUpdatePresenceMessage(message) {
const user = state.users[message.actor];
if (user == null) {
state.users[message.actor] = {
connectionId: message.actor,
presence: message.data,
};
}
else {
state.users[message.actor] = {
id: user.id,
info: user.info,
connectionId: message.actor,
presence: Object.assign(Object.assign({}, user.presence), message.data),
};
}
updateUsers({
type: "update",
updates: message.data,
user: state.users[message.actor],
});
}
function updateUsers(event) {
state.others = makeOthers(state.users);
for (const listener of state.listeners["others"]) {
listener(state.others, event);
}
}
function onUserLeftMessage(message) {
const userLeftMessage = message;
const user = state.users[userLeftMessage.actor];
if (user) {
delete state.users[userLeftMessage.actor];
updateUsers({ type: "leave", user });
}
}
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,
};
}
state.users = newUsers;
updateUsers({ type: "reset" });
}
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) {
state.users[message.actor] = {
connectionId: message.actor,
info: message.info,
id: message.id,
};
updateUsers({ type: "enter", user: state.users[message.actor] });
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);
switch (message.type) {
case ServerMessageType.InitialStorageState: {
onInitialStorageState(message);
break;
}
case ServerMessageType.UpdateStorage: {
onStorageUpdates(message);
break;
}
case ServerMessageType.UserJoined: {
onUserJoinedMessage(message);
break;
}
case ServerMessageType.UpdatePresence: {
onUpdatePresenceMessage(message);
break;
}
case ServerMessageType.Event: {
onEvent(message);
break;
}
case ServerMessageType.UserLeft: {
onUserLeftMessage(message);
break;
}
case ServerMessageType.RoomState: {
onRoomStateMessage(message);
break;
}
}
}
// 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);
state.users = {};
updateUsers({ type: "reset" });
if (event.code >= 4000 && event.code <= 4100) {
updateConnection({ state: "failed" });
const error = new LiveblocksError(event.reason, event.code);
for (const listener of state.listeners.error) {
listener(error);
}
}
else if (event.wasClean === false) {
updateConnection({ state: "unavailable" });
state.numberOfRetry++;
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
}
else {
updateConnection({ state: "closed" });
}
}
function updateConnection(connection) {
state.connection = connection;
for (const listener of state.listeners.connection) {
listener(connection.state);
}
}
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();
if (state.connection.state === "connecting") {
updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
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;
}
clearTimeout(state.timeoutHandles.pongTimeout);
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
if (state.socket.readyState === WebSocket.OPEN) {
state.socket.send("ping");
}
}
function pongTimeout() {
reconnect();
}
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;
}
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();
const elapsedTime = now - state.lastFlushTime;
if (elapsedTime > context.throttleDelay) {
const messages = flushDataToMessages(state);
if (messages.length === 0) {
return;
}
effects.send(messages);
state.flushData = {
messages: [],
storageOperations: [],
presence: null,
};
state.lastFlushTime = now;
}
else {
if (state.timeoutHandles.flush != null) {
clearTimeout(state.timeoutHandles.flush);
}
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" });
if (state.timeoutHandles.flush) {
clearTimeout(state.timeoutHandles.flush);
}
clearTimeout(state.timeoutHandles.reconnect);
clearTimeout(state.timeoutHandles.pongTimeout);
clearInterval(state.intervalHandles.heartbeat);
state.users = {};
updateUsers({ type: "reset" });
clearListeners();
}
function clearListeners() {
for (const key in state.listeners) {
state.listeners[key] = [];
}
}
function getPresence() {
return state.me;
}
function getOthers() {
return state.others;
}
function broadcastEvent(event) {
if (state.socket == null) {
return;
}
state.flushData.messages.push({
type: ClientMessageType.ClientEvent,
event,
});
tryFlushing();
}
/**
* STORAGE
*/
function onStorageUpdates(message) {
if (state.doc == null) {
// TODO: Cache updates in case they are coming while root is queried
return;
}
updateDoc(message.ops.reduce((doc, op) => doc.dispatch(op), state.doc));
}
function updateDoc(doc) {
state.doc = doc;
if (doc) {
for (const listener of state.listeners.storage) {
listener(getStorage());
}
}
}
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 deleteItemById(list, itemId) {
updateDoc(state.doc.deleteItemById(list.id, itemId));
}
function moveItem(list, index, targetIndex) {
updateDoc(state.doc.moveItem(list.id, index, targetIndex));
}
return {
// Internal
onOpen,
onClose,
onMessage,
authenticationSuccess,
heartbeat,
onNavigatorOnline,
// onWakeUp,
onVisibilityChange,
// Core
connect,
disconnect,
subscribe,
unsubscribe,
// Presence
updatePresence,
broadcastEvent,
// Storage
fetchStorage,
createRecord: createRecord$1,
updateRecord,
createList: createList$1,
pushItem,
deleteItem,
deleteItemById,
moveItem,
selectors: {
// Core
getConnectionState,
getCurrentUser,
// Presence
getPresence,
getOthers,
// Storage
getStorage,
},
};
}
function defaultState(me) {
return {
connection: { state: "closed" },
socket: null,
listeners: {
storage: [],
event: [],
others: [],
"my-presence": [],
error: [],
connection: [],
},
numberOfRetry: 0,
lastFlushTime: 0,
timeoutHandles: {
flush: null,
reconnect: 0,
pongTimeout: 0,
},
flushData: {
presence: me == null ? {} : me,
messages: [],
storageOperations: [],
},
intervalHandles: {
heartbeat: 0,
},
me: me == null ? {} : me,
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://liveblocks.net";
const authEndpoint = options.authEndpoint;
const state = defaultState(options.initialPresence);
const machine = makeStateMachine(state, {
throttleDelay,
liveblocksServer,
authEndpoint,
room: name,
});
const room = {
/////////////
// Core //
/////////////
getConnectionState: machine.selectors.getConnectionState,
getCurrentUser: machine.selectors.getCurrentUser,
subscribe: machine.subscribe,
unsubscribe: machine.unsubscribe,
/////////////
// Storage //
/////////////
getStorage: machine.selectors.getStorage,
fetchStorage: machine.fetchStorage,
createRecord: machine.createRecord,
createList: machine.createList,
updateRecord: machine.updateRecord,
pushItem: machine.pushItem,
deleteItem: machine.deleteItem,
deleteItemById: machine.deleteItemById,
moveItem: machine.moveItem,
//////////////
// Presence //
//////////////
getPresence: machine.selectors.getPresence,
updatePresence: machine.updatePresence,
getOthers: machine.selectors.getOthers,
broadcastEvent: machine.broadcastEvent,
};
return {
connect: machine.connect,
disconnect: machine.disconnect,
onNavigatorOnline: machine.onNavigatorOnline,
onVisibilityChange: machine.onVisibilityChange,
room,
};
}
class LiveblocksError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
/**
* Create a client that will be responsible to communicate with liveblocks servers.
*
* ### Example
* ```
* const client = createClient({
* authEndpoint: "/api/auth"
* })
* ```
*/
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();
function getRoom(roomId) {
const internalRoom = rooms.get(roomId);
return internalRoom ? internalRoom.room : null;
}
function enter(roomId, initialPresence) {
let internalRoom = rooms.get(roomId);
if (internalRoom) {
return internalRoom.room;
}
internalRoom = createRoom(roomId, Object.assign(Object.assign({}, options), { initialPresence }));
rooms.set(roomId, internalRoom);
internalRoom.connect();
return internalRoom.room;
}
function leave(roomId) {
let room = rooms.get(roomId);
if (room) {
room.disconnect();
rooms.delete(roomId);
}
}
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 {
getRoom,
enter,
leave,
};
}
var ClientContext = React.createContext(null);

@@ -1691,3 +368,2 @@ var RoomContext = React.createContext(null);

exports.RoomProvider = RoomProvider;
exports.createClient = createClient;
exports.useBroadcastEvent = useBroadcastEvent;

@@ -1694,0 +370,0 @@ exports.useCurrentUser = useCurrentUser;

{
"name": "@liveblocks/react",
"version": "0.9.0",
"version": "0.10.0",
"description": "",

@@ -25,6 +25,4 @@ "main": "./lib/index.js",

"license": "Apache-2.0",
"dependencies": {
"@liveblocks/client": "0.9.0"
},
"peerDependencies": {
"@liveblocks/client": "0.10.0",
"react": "^16.14.0 || ^17"

@@ -31,0 +29,0 @@ },

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc