@liveblocks/client
Advanced tools
Comparing version 0.1.3 to 0.2.0
@@ -1,54 +0,2 @@ | ||
import { Record, List, RecordData } from "./doc"; | ||
export declare type Users<T extends UserState = UserState> = { | ||
[id: string]: T | null; | ||
}; | ||
declare type OnMyPresenceChange = (me: UserState) => void; | ||
declare type OnOthersPresenceChange = (others: Users) => void; | ||
export declare type Client = ReturnType<typeof createClient>; | ||
declare type Options = { | ||
authEndpoint: string; | ||
liveblocksServer?: string; | ||
}; | ||
declare type CreateRecord = Room["createRecord"]; | ||
declare type CreateList = Room["createList"]; | ||
export declare type InitialRootFactory<TRoot = RecordData> = (factories: { | ||
createRecord: CreateRecord; | ||
createList: CreateList; | ||
}) => TRoot; | ||
export declare function createClient(options: Options): { | ||
createRecord<T extends RecordData>(room: string, data: any): Record<T>; | ||
createList<T_1 extends List<any> | Record<any>>(room: string): List<T_1>; | ||
updateRecord<T_2 extends RecordData>(room: string, record: Record<T_2>, overrides: Partial<T_2>): void; | ||
pushItem<T_3 extends List<any> | Record<any>>(room: string, list: List<T_3>, item: T_3): void; | ||
deleteItem<T_4 extends List<any> | Record<any>>(room: string, list: List<T_4>, index: number): void; | ||
moveItem<T_5 extends List<any> | Record<any>>(room: string, list: List<T_5>, index: number, targetIndex: number): void; | ||
observeStorage: <TRoot extends RecordData>(room: string, callback: (root: Record<TRoot> | null) => void, initialDocumentState: InitialRootFactory<TRoot>) => void; | ||
unobserveStorage: <TRoot_1 extends RecordData>(room: string, callback: (root: Record<TRoot_1> | null) => void) => void; | ||
getPresence: <TUser extends UserState>(room: string) => TUser | null; | ||
setPresence: (room: string, overrides: Partial<UserState>) => void; | ||
observeMyPresence: (room: string, callback: OnMyPresenceChange) => void; | ||
unobserveMyPresence: (room: string, callback: OnMyPresenceChange) => void; | ||
observeOthersPresence: (room: string, callback: OnOthersPresenceChange) => void; | ||
unobserveOthersPresence: (room: string, callback: OnOthersPresenceChange) => void; | ||
}; | ||
export declare type Room = ReturnType<typeof createRoom>; | ||
declare function createRoom(name: string, options: Options): { | ||
createRecord: <T extends RecordData>(data: any) => Record<T>; | ||
createList: <T_1 extends List<any> | Record<any>>() => List<T_1>; | ||
updateRecord<T_2 extends RecordData>(record: Record<T_2>, overrides: Partial<T_2>): void; | ||
pushItem<T_3 extends List<any> | Record<any>>(list: List<T_3>, item: T_3): void; | ||
deleteItem<T_4 extends List<any> | Record<any>>(list: List<T_4>, index: number): void; | ||
moveItem<T_5 extends List<any> | Record<any>>(list: List<T_5>, index: number, targetIndex: number): void; | ||
observeStorage: <TRoot extends RecordData>(callback: (root: Record<TRoot> | null) => void, initialDocumentState: InitialRootFactory<TRoot>) => void; | ||
unobserveStorage: <TRoot_1 extends RecordData>(callback: (root: Record<TRoot_1> | null) => void) => void; | ||
getPresence: <TUser extends UserState>() => TUser | null; | ||
setPresence: <TUser_1 extends UserState>(overrides: Partial<TUser_1>) => void; | ||
observeMyPresence: <TUser_2 extends UserState>(callback: (presence: TUser_2) => void) => void; | ||
unobserveMyPresence: <TUser_3 extends UserState>(callback: (presence: TUser_3) => void) => void; | ||
observeOthersPresence: <TUser_4 extends UserState>(callback: (users: Users<TUser_4>) => void) => void; | ||
unobserveOthersPresence: <TUser_5 extends UserState>(callback: (users: Users<TUser_5>) => void) => void; | ||
}; | ||
export declare type UserState = { | ||
[key: string]: boolean | string | number; | ||
}; | ||
export {}; | ||
import { ClientOptions, Client } from "./types"; | ||
export declare function createClient(options: ClientOptions): Client; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createClient = void 0; | ||
const doc_1 = require("./doc"); | ||
const WAIT = 100; | ||
function makeIdFactory(actor) { | ||
let count = 0; | ||
return () => `${actor}:${count++}`; | ||
} | ||
function remove(array, item) { | ||
for (let i = 0; i < array.length; i++) { | ||
if (array[i] === item) { | ||
array.splice(i, 1); | ||
break; | ||
const room_1 = require("./room"); | ||
const utils_1 = require("./utils"); | ||
function createClient(options) { | ||
const rooms = new Map(); | ||
const _listeners = { | ||
error: [], | ||
}; | ||
function onError(error) { | ||
for (const listener of _listeners.error) { | ||
listener(error); | ||
} | ||
} | ||
} | ||
function authorize(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) { | ||
console.error("FAIL"); | ||
} | ||
const authResponse = yield res.json(); | ||
return { | ||
token: authResponse.token, | ||
appId: authResponse.appId, | ||
actor: authResponse.actor, | ||
}; | ||
}); | ||
} | ||
function createClient(options) { | ||
const rooms = new Map(); | ||
function getRoomOrInit(name) { | ||
let room = rooms.get(name); | ||
if (room == null) { | ||
room = createRoom(name, options); | ||
room = room_1.createRoom(name, Object.assign(Object.assign({}, options), { onError })); | ||
rooms.set(name, room); | ||
@@ -70,338 +24,23 @@ } | ||
} | ||
return { | ||
///////////// | ||
// Storage // | ||
///////////// | ||
createRecord(room, data) { | ||
return getRoomOrInit(room).createRecord(data); | ||
}, | ||
createList(room) { | ||
return getRoomOrInit(room).createList(); | ||
}, | ||
updateRecord(room, record, overrides) { | ||
getRoomOrInit(room).updateRecord(record, overrides); | ||
}, | ||
pushItem(room, list, item) { | ||
getRoomOrInit(room).pushItem(list, item); | ||
}, | ||
deleteItem(room, list, index) { | ||
getRoomOrInit(room).deleteItem(list, index); | ||
}, | ||
moveItem(room, list, index, targetIndex) { | ||
getRoomOrInit(room).moveItem(list, index, targetIndex); | ||
}, | ||
observeStorage: (room, callback, initialDocumentState) => { | ||
getRoomOrInit(room).observeStorage(callback, initialDocumentState); | ||
}, | ||
unobserveStorage: (room, callback) => { | ||
getRoomOrInit(room).unobserveStorage(callback); | ||
}, | ||
////////////// | ||
// Presence // | ||
////////////// | ||
getPresence: (room) => { | ||
return getRoomOrInit(room).getPresence(); | ||
}, | ||
setPresence: (room, overrides) => { | ||
return getRoomOrInit(room).setPresence(overrides); | ||
}, | ||
observeMyPresence: (room, callback) => { | ||
return getRoomOrInit(room).observeMyPresence(callback); | ||
}, | ||
unobserveMyPresence: (room, callback) => { | ||
return getRoomOrInit(room).unobserveMyPresence(callback); | ||
}, | ||
observeOthersPresence: (room, callback) => { | ||
return getRoomOrInit(room).observeOthersPresence(callback); | ||
}, | ||
unobserveOthersPresence: (room, callback) => { | ||
return getRoomOrInit(room).unobserveOthersPresence(callback); | ||
}, | ||
}; | ||
} | ||
exports.createClient = createClient; | ||
var RoomState; | ||
(function (RoomState) { | ||
RoomState[RoomState["Default"] = 0] = "Default"; | ||
RoomState[RoomState["Connecting"] = 1] = "Connecting"; | ||
RoomState[RoomState["Connected"] = 2] = "Connected"; | ||
})(RoomState || (RoomState = {})); | ||
function createRoom(name, options) { | ||
const liveblocksServer = options.liveblocksServer; | ||
const authEndpoint = options.authEndpoint; | ||
let _listeners = { | ||
onRootChange: [], | ||
onOthersPresenceChange: [], | ||
onPresenceChange: [], | ||
}; | ||
let _idFactory = null; | ||
let _socket = null; | ||
let _doc = null; | ||
let toFlush = []; | ||
let _lastEmit = 0; | ||
let _timeout = null; | ||
let _initialRootFactory = null; | ||
let _me = null; | ||
let _users = {}; | ||
let state = RoomState.Default; | ||
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"); | ||
} | ||
_socket.send(JSON.stringify(clientMessage)); | ||
function getRoom(room) { | ||
return getRoomOrInit(room); | ||
} | ||
function makeId() { | ||
if (_idFactory == null) { | ||
throw new Error("Can't generate id. Id factory is missing."); | ||
function addEventListener(type, listener) { | ||
if (type !== "error") { | ||
throw new Error(`"${type}" is not a valid event name`); | ||
} | ||
return _idFactory(); | ||
_listeners.error.push(listener); | ||
} | ||
function updateUsers(users) { | ||
_users = users; | ||
for (const listener of _listeners.onOthersPresenceChange) { | ||
listener(users); | ||
function removeEventListener(type, listener) { | ||
if (type !== "error") { | ||
throw new Error(`"${type}" is not a valid event name`); | ||
} | ||
utils_1.remove(_listeners.error, listener); | ||
} | ||
function dispatch(op) { | ||
toFlush.push(op); | ||
const now = Date.now(); | ||
if (now - _lastEmit > WAIT) { | ||
send({ | ||
type: ClientMessageType.UpdateDocument, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = now; | ||
return; | ||
} | ||
if (_timeout) { | ||
clearTimeout(_timeout); | ||
_timeout = null; | ||
} | ||
_timeout = setTimeout(() => { | ||
send({ | ||
type: ClientMessageType.UpdateDocument, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = Date.now(); | ||
}, WAIT - (now - _lastEmit)); | ||
} | ||
function updateDoc(doc) { | ||
_doc = doc; | ||
if (doc) { | ||
for (const listener of _listeners.onRootChange) { | ||
listener(doc.root); | ||
} | ||
} | ||
} | ||
function createRecord(data) { | ||
return doc_1.createRecord(makeId(), data); | ||
} | ||
function createList() { | ||
return doc_1.createList(makeId()); | ||
} | ||
function updatePresence(me) { | ||
_me = me; | ||
for (const listener of _listeners.onPresenceChange) { | ||
listener(_me); | ||
} | ||
} | ||
function onGetDocument(message) { | ||
if (message.root == null) { | ||
const rootId = makeId(); | ||
_doc = doc_1.Doc.empty(rootId, (op) => dispatch(op)); | ||
updateDoc(_doc.updateRecord(rootId, _initialRootFactory({ | ||
createRecord: (data) => createRecord(data), | ||
createList: () => createList(), | ||
}))); | ||
} | ||
else { | ||
updateDoc(doc_1.Doc.load(message.root, (op) => dispatch(op))); | ||
} | ||
} | ||
function onDocumentUpdates(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 connect(onSocketOpen = () => { }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (state === RoomState.Connected) { | ||
onSocketOpen(); | ||
return; | ||
} | ||
if (state === RoomState.Connecting) { | ||
onSocketOpenCallbacks.push(onSocketOpen); | ||
// Already connected to this room | ||
return; | ||
} | ||
onSocketOpenCallbacks.push(onSocketOpen); | ||
state = RoomState.Connecting; | ||
const { token, actor } = yield authorize(authEndpoint, name); | ||
_idFactory = makeIdFactory(actor); | ||
_socket = new WebSocket(`${liveblocksServer}/?token=${token}`); | ||
_socket.addEventListener("message", function (event) { | ||
onMessage(JSON.parse(event.data)); | ||
}); | ||
_socket.addEventListener("open", () => { | ||
console.log("on open"); | ||
for (const callback of onSocketOpenCallbacks) { | ||
callback(); | ||
} | ||
state = RoomState.Connected; | ||
onSocketOpenCallbacks = []; | ||
}); | ||
}); | ||
} | ||
function disconnect() { | ||
// TODO | ||
} | ||
const onMessage = (message) => { | ||
switch (message.type) { | ||
case ServerMessageType.InitialDocumentState: { | ||
onGetDocument(message); | ||
break; | ||
} | ||
case ServerMessageType.DocumentOperations: { | ||
onDocumentUpdates(message); | ||
break; | ||
} | ||
case ServerMessageType.UserJoined: { | ||
// Send current presence to new user | ||
send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: _me, | ||
targetActor: message.actor, | ||
}); | ||
break; | ||
} | ||
case ServerMessageType.UpdateUserState: { | ||
const currentUser = _users[message.actor]; | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: currentUser | ||
? Object.assign(Object.assign({}, currentUser), message.data) : message.data })); | ||
break; | ||
} | ||
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); | ||
break; | ||
} | ||
} | ||
}; | ||
////////////// | ||
// Presence // | ||
////////////// | ||
function getPresence() { | ||
return _me; | ||
} | ||
function setPresence(overrides) { | ||
updatePresence(Object.assign(Object.assign({}, _me), overrides)); | ||
if (state !== RoomState.Connected) { | ||
onSocketOpenCallbacks.push(() => send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: _me, | ||
})); | ||
return; | ||
} | ||
const now = Date.now(); | ||
if (now - _lastPresenceEmit > WAIT) { | ||
send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: Object.assign(Object.assign({}, _toSend), overrides), | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = now; | ||
return; | ||
} | ||
_toSend = Object.assign(Object.assign({}, _toSend), overrides); | ||
if (_presenceTimeout) { | ||
clearTimeout(_presenceTimeout); | ||
_presenceTimeout = null; | ||
} | ||
_presenceTimeout = setTimeout(() => { | ||
send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: _toSend, | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = Date.now(); | ||
}, WAIT - (now - _lastPresenceEmit)); | ||
} | ||
function observeMyPresence(callback) { | ||
_listeners.onPresenceChange.push(callback); | ||
connect(); | ||
} | ||
function unobserveMyPresence(callback) { | ||
remove(_listeners.onPresenceChange, callback); | ||
disconnect(); | ||
} | ||
function observeOthersPresence(callback) { | ||
_listeners.onOthersPresenceChange.push(callback); | ||
connect(); | ||
} | ||
function unobserveOthersPresence(callback) { | ||
remove(_listeners.onOthersPresenceChange, callback); | ||
disconnect(); | ||
} | ||
return { | ||
///////////// | ||
// Storage // | ||
///////////// | ||
createRecord, | ||
createList, | ||
updateRecord(record, overrides) { | ||
updateDoc(_doc.updateRecord(record.id, overrides)); | ||
}, | ||
pushItem(list, item) { | ||
updateDoc(_doc.pushItem(list.id, item)); | ||
}, | ||
deleteItem(list, index) { | ||
updateDoc(_doc.deleteItem(list.id, index)); | ||
}, | ||
moveItem(list, index, targetIndex) { | ||
updateDoc(_doc.moveItem(list.id, index, targetIndex)); | ||
}, | ||
observeStorage: (callback, initialDocumentState) => { | ||
connect(() => { | ||
_initialRootFactory = initialDocumentState; | ||
send({ type: ClientMessageType.GetDocument }); | ||
}); | ||
_listeners.onRootChange.push(callback); | ||
}, | ||
unobserveStorage: (callback) => { | ||
remove(_listeners.onRootChange, callback); | ||
disconnect(); | ||
}, | ||
////////////// | ||
// Presence // | ||
////////////// | ||
getPresence, | ||
setPresence, | ||
observeMyPresence, | ||
unobserveMyPresence, | ||
observeOthersPresence, | ||
unobserveOthersPresence, | ||
addEventListener, | ||
removeEventListener, | ||
getRoom, | ||
}; | ||
} | ||
var ServerMessageType; | ||
(function (ServerMessageType) { | ||
ServerMessageType[ServerMessageType["UpdateUserState"] = 100] = "UpdateUserState"; | ||
ServerMessageType[ServerMessageType["UserJoined"] = 101] = "UserJoined"; | ||
ServerMessageType[ServerMessageType["UserLeft"] = 102] = "UserLeft"; | ||
ServerMessageType[ServerMessageType["InitialDocumentState"] = 200] = "InitialDocumentState"; | ||
ServerMessageType[ServerMessageType["DocumentOperations"] = 201] = "DocumentOperations"; | ||
})(ServerMessageType || (ServerMessageType = {})); | ||
var ClientMessageType; | ||
(function (ClientMessageType) { | ||
ClientMessageType[ClientMessageType["UpdateUserState"] = 100] = "UpdateUserState"; | ||
ClientMessageType[ClientMessageType["GetDocument"] = 200] = "GetDocument"; | ||
ClientMessageType[ClientMessageType["UpdateDocument"] = 201] = "UpdateDocument"; | ||
})(ClientMessageType || (ClientMessageType = {})); | ||
exports.createClient = createClient; |
@@ -384,3 +384,4 @@ "use strict"; | ||
if (key !== "id" && key !== "type") { | ||
serializedData[key] = serialize(record[key]); | ||
const value = record[key]; // TODO: Find out why typescript does not like that | ||
serializedData[key] = serialize(value); | ||
} | ||
@@ -387,0 +388,0 @@ } |
export type { Record, RecordData, List } from "./doc"; | ||
export type { Users, Client, Room, UserState, InitialRootFactory, } from "./client"; | ||
export { createClient } from "./client"; | ||
export { RoomState } from "./types"; | ||
export type { Others, Presence, Room, InitialStorageFactory, Client, } from "./types"; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createClient = void 0; | ||
exports.RoomState = exports.createClient = void 0; | ||
var client_1 = require("./client"); | ||
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } }); | ||
var types_1 = require("./types"); | ||
Object.defineProperty(exports, "RoomState", { enumerable: true, get: function () { return types_1.RoomState; } }); |
@@ -1,54 +0,2 @@ | ||
import { Record, List, RecordData } from "./doc"; | ||
export declare type Users<T extends UserState = UserState> = { | ||
[id: string]: T | null; | ||
}; | ||
declare type OnMyPresenceChange = (me: UserState) => void; | ||
declare type OnOthersPresenceChange = (others: Users) => void; | ||
export declare type Client = ReturnType<typeof createClient>; | ||
declare type Options = { | ||
authEndpoint: string; | ||
liveblocksServer?: string; | ||
}; | ||
declare type CreateRecord = Room["createRecord"]; | ||
declare type CreateList = Room["createList"]; | ||
export declare type InitialRootFactory<TRoot = RecordData> = (factories: { | ||
createRecord: CreateRecord; | ||
createList: CreateList; | ||
}) => TRoot; | ||
export declare function createClient(options: Options): { | ||
createRecord<T extends RecordData>(room: string, data: any): Record<T>; | ||
createList<T_1 extends List<any> | Record<any>>(room: string): List<T_1>; | ||
updateRecord<T_2 extends RecordData>(room: string, record: Record<T_2>, overrides: Partial<T_2>): void; | ||
pushItem<T_3 extends List<any> | Record<any>>(room: string, list: List<T_3>, item: T_3): void; | ||
deleteItem<T_4 extends List<any> | Record<any>>(room: string, list: List<T_4>, index: number): void; | ||
moveItem<T_5 extends List<any> | Record<any>>(room: string, list: List<T_5>, index: number, targetIndex: number): void; | ||
observeStorage: <TRoot extends RecordData>(room: string, callback: (root: Record<TRoot> | null) => void, initialDocumentState: InitialRootFactory<TRoot>) => void; | ||
unobserveStorage: <TRoot_1 extends RecordData>(room: string, callback: (root: Record<TRoot_1> | null) => void) => void; | ||
getPresence: <TUser extends UserState>(room: string) => TUser | null; | ||
setPresence: (room: string, overrides: Partial<UserState>) => void; | ||
observeMyPresence: (room: string, callback: OnMyPresenceChange) => void; | ||
unobserveMyPresence: (room: string, callback: OnMyPresenceChange) => void; | ||
observeOthersPresence: (room: string, callback: OnOthersPresenceChange) => void; | ||
unobserveOthersPresence: (room: string, callback: OnOthersPresenceChange) => void; | ||
}; | ||
export declare type Room = ReturnType<typeof createRoom>; | ||
declare function createRoom(name: string, options: Options): { | ||
createRecord: <T extends RecordData>(data: any) => Record<T>; | ||
createList: <T_1 extends List<any> | Record<any>>() => List<T_1>; | ||
updateRecord<T_2 extends RecordData>(record: Record<T_2>, overrides: Partial<T_2>): void; | ||
pushItem<T_3 extends List<any> | Record<any>>(list: List<T_3>, item: T_3): void; | ||
deleteItem<T_4 extends List<any> | Record<any>>(list: List<T_4>, index: number): void; | ||
moveItem<T_5 extends List<any> | Record<any>>(list: List<T_5>, index: number, targetIndex: number): void; | ||
observeStorage: <TRoot extends RecordData>(callback: (root: Record<TRoot> | null) => void, initialDocumentState: InitialRootFactory<TRoot>) => void; | ||
unobserveStorage: <TRoot_1 extends RecordData>(callback: (root: Record<TRoot_1> | null) => void) => void; | ||
getPresence: <TUser extends UserState>() => TUser | null; | ||
setPresence: <TUser_1 extends UserState>(overrides: Partial<TUser_1>) => void; | ||
observeMyPresence: <TUser_2 extends UserState>(callback: (presence: TUser_2) => void) => void; | ||
unobserveMyPresence: <TUser_3 extends UserState>(callback: (presence: TUser_3) => void) => void; | ||
observeOthersPresence: <TUser_4 extends UserState>(callback: (users: Users<TUser_4>) => void) => void; | ||
unobserveOthersPresence: <TUser_5 extends UserState>(callback: (users: Users<TUser_5>) => void) => void; | ||
}; | ||
export declare type UserState = { | ||
[key: string]: boolean | string | number; | ||
}; | ||
export {}; | ||
import { ClientOptions, Client } from "./types"; | ||
export declare function createClient(options: ClientOptions): Client; |
@@ -1,63 +0,17 @@ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
import { createRoom } from "./room"; | ||
import { remove } from "./utils"; | ||
export function createClient(options) { | ||
const rooms = new Map(); | ||
const _listeners = { | ||
error: [], | ||
}; | ||
function onError(error) { | ||
for (const listener of _listeners.error) { | ||
listener(error); | ||
} | ||
return t; | ||
}; | ||
import { Doc, createRecord as innerCreateRecord, createList as innerCreateList, } from "./doc"; | ||
const WAIT = 100; | ||
function makeIdFactory(actor) { | ||
let count = 0; | ||
return () => `${actor}:${count++}`; | ||
} | ||
function remove(array, item) { | ||
for (let i = 0; i < array.length; i++) { | ||
if (array[i] === item) { | ||
array.splice(i, 1); | ||
break; | ||
} | ||
} | ||
} | ||
function authorize(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) { | ||
console.error("FAIL"); | ||
} | ||
const authResponse = yield res.json(); | ||
return { | ||
token: authResponse.token, | ||
appId: authResponse.appId, | ||
actor: authResponse.actor, | ||
}; | ||
}); | ||
} | ||
export function createClient(options) { | ||
const rooms = new Map(); | ||
function getRoomOrInit(name) { | ||
let room = rooms.get(name); | ||
if (room == null) { | ||
room = createRoom(name, options); | ||
room = createRoom(name, Object.assign(Object.assign({}, options), { onError })); | ||
rooms.set(name, room); | ||
@@ -67,337 +21,22 @@ } | ||
} | ||
return { | ||
///////////// | ||
// Storage // | ||
///////////// | ||
createRecord(room, data) { | ||
return getRoomOrInit(room).createRecord(data); | ||
}, | ||
createList(room) { | ||
return getRoomOrInit(room).createList(); | ||
}, | ||
updateRecord(room, record, overrides) { | ||
getRoomOrInit(room).updateRecord(record, overrides); | ||
}, | ||
pushItem(room, list, item) { | ||
getRoomOrInit(room).pushItem(list, item); | ||
}, | ||
deleteItem(room, list, index) { | ||
getRoomOrInit(room).deleteItem(list, index); | ||
}, | ||
moveItem(room, list, index, targetIndex) { | ||
getRoomOrInit(room).moveItem(list, index, targetIndex); | ||
}, | ||
observeStorage: (room, callback, initialDocumentState) => { | ||
getRoomOrInit(room).observeStorage(callback, initialDocumentState); | ||
}, | ||
unobserveStorage: (room, callback) => { | ||
getRoomOrInit(room).unobserveStorage(callback); | ||
}, | ||
////////////// | ||
// Presence // | ||
////////////// | ||
getPresence: (room) => { | ||
return getRoomOrInit(room).getPresence(); | ||
}, | ||
setPresence: (room, overrides) => { | ||
return getRoomOrInit(room).setPresence(overrides); | ||
}, | ||
observeMyPresence: (room, callback) => { | ||
return getRoomOrInit(room).observeMyPresence(callback); | ||
}, | ||
unobserveMyPresence: (room, callback) => { | ||
return getRoomOrInit(room).unobserveMyPresence(callback); | ||
}, | ||
observeOthersPresence: (room, callback) => { | ||
return getRoomOrInit(room).observeOthersPresence(callback); | ||
}, | ||
unobserveOthersPresence: (room, callback) => { | ||
return getRoomOrInit(room).unobserveOthersPresence(callback); | ||
}, | ||
}; | ||
} | ||
var RoomState; | ||
(function (RoomState) { | ||
RoomState[RoomState["Default"] = 0] = "Default"; | ||
RoomState[RoomState["Connecting"] = 1] = "Connecting"; | ||
RoomState[RoomState["Connected"] = 2] = "Connected"; | ||
})(RoomState || (RoomState = {})); | ||
function createRoom(name, options) { | ||
const liveblocksServer = options.liveblocksServer; | ||
const authEndpoint = options.authEndpoint; | ||
let _listeners = { | ||
onRootChange: [], | ||
onOthersPresenceChange: [], | ||
onPresenceChange: [], | ||
}; | ||
let _idFactory = null; | ||
let _socket = null; | ||
let _doc = null; | ||
let toFlush = []; | ||
let _lastEmit = 0; | ||
let _timeout = null; | ||
let _initialRootFactory = null; | ||
let _me = null; | ||
let _users = {}; | ||
let state = RoomState.Default; | ||
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"); | ||
} | ||
_socket.send(JSON.stringify(clientMessage)); | ||
function getRoom(room) { | ||
return getRoomOrInit(room); | ||
} | ||
function makeId() { | ||
if (_idFactory == null) { | ||
throw new Error("Can't generate id. Id factory is missing."); | ||
function addEventListener(type, listener) { | ||
if (type !== "error") { | ||
throw new Error(`"${type}" is not a valid event name`); | ||
} | ||
return _idFactory(); | ||
_listeners.error.push(listener); | ||
} | ||
function updateUsers(users) { | ||
_users = users; | ||
for (const listener of _listeners.onOthersPresenceChange) { | ||
listener(users); | ||
function removeEventListener(type, listener) { | ||
if (type !== "error") { | ||
throw new Error(`"${type}" is not a valid event name`); | ||
} | ||
remove(_listeners.error, listener); | ||
} | ||
function dispatch(op) { | ||
toFlush.push(op); | ||
const now = Date.now(); | ||
if (now - _lastEmit > WAIT) { | ||
send({ | ||
type: ClientMessageType.UpdateDocument, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = now; | ||
return; | ||
} | ||
if (_timeout) { | ||
clearTimeout(_timeout); | ||
_timeout = null; | ||
} | ||
_timeout = setTimeout(() => { | ||
send({ | ||
type: ClientMessageType.UpdateDocument, | ||
ops: toFlush, | ||
}); | ||
toFlush = []; | ||
_lastEmit = Date.now(); | ||
}, WAIT - (now - _lastEmit)); | ||
} | ||
function updateDoc(doc) { | ||
_doc = doc; | ||
if (doc) { | ||
for (const listener of _listeners.onRootChange) { | ||
listener(doc.root); | ||
} | ||
} | ||
} | ||
function createRecord(data) { | ||
return innerCreateRecord(makeId(), data); | ||
} | ||
function createList() { | ||
return innerCreateList(makeId()); | ||
} | ||
function updatePresence(me) { | ||
_me = me; | ||
for (const listener of _listeners.onPresenceChange) { | ||
listener(_me); | ||
} | ||
} | ||
function onGetDocument(message) { | ||
if (message.root == null) { | ||
const rootId = makeId(); | ||
_doc = Doc.empty(rootId, (op) => dispatch(op)); | ||
updateDoc(_doc.updateRecord(rootId, _initialRootFactory({ | ||
createRecord: (data) => createRecord(data), | ||
createList: () => createList(), | ||
}))); | ||
} | ||
else { | ||
updateDoc(Doc.load(message.root, (op) => dispatch(op))); | ||
} | ||
} | ||
function onDocumentUpdates(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 connect(onSocketOpen = () => { }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (state === RoomState.Connected) { | ||
onSocketOpen(); | ||
return; | ||
} | ||
if (state === RoomState.Connecting) { | ||
onSocketOpenCallbacks.push(onSocketOpen); | ||
// Already connected to this room | ||
return; | ||
} | ||
onSocketOpenCallbacks.push(onSocketOpen); | ||
state = RoomState.Connecting; | ||
const { token, actor } = yield authorize(authEndpoint, name); | ||
_idFactory = makeIdFactory(actor); | ||
_socket = new WebSocket(`${liveblocksServer}/?token=${token}`); | ||
_socket.addEventListener("message", function (event) { | ||
onMessage(JSON.parse(event.data)); | ||
}); | ||
_socket.addEventListener("open", () => { | ||
console.log("on open"); | ||
for (const callback of onSocketOpenCallbacks) { | ||
callback(); | ||
} | ||
state = RoomState.Connected; | ||
onSocketOpenCallbacks = []; | ||
}); | ||
}); | ||
} | ||
function disconnect() { | ||
// TODO | ||
} | ||
const onMessage = (message) => { | ||
switch (message.type) { | ||
case ServerMessageType.InitialDocumentState: { | ||
onGetDocument(message); | ||
break; | ||
} | ||
case ServerMessageType.DocumentOperations: { | ||
onDocumentUpdates(message); | ||
break; | ||
} | ||
case ServerMessageType.UserJoined: { | ||
// Send current presence to new user | ||
send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: _me, | ||
targetActor: message.actor, | ||
}); | ||
break; | ||
} | ||
case ServerMessageType.UpdateUserState: { | ||
const currentUser = _users[message.actor]; | ||
updateUsers(Object.assign(Object.assign({}, _users), { [message.actor]: currentUser | ||
? Object.assign(Object.assign({}, currentUser), message.data) : message.data })); | ||
break; | ||
} | ||
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); | ||
break; | ||
} | ||
} | ||
}; | ||
////////////// | ||
// Presence // | ||
////////////// | ||
function getPresence() { | ||
return _me; | ||
} | ||
function setPresence(overrides) { | ||
updatePresence(Object.assign(Object.assign({}, _me), overrides)); | ||
if (state !== RoomState.Connected) { | ||
onSocketOpenCallbacks.push(() => send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: _me, | ||
})); | ||
return; | ||
} | ||
const now = Date.now(); | ||
if (now - _lastPresenceEmit > WAIT) { | ||
send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: Object.assign(Object.assign({}, _toSend), overrides), | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = now; | ||
return; | ||
} | ||
_toSend = Object.assign(Object.assign({}, _toSend), overrides); | ||
if (_presenceTimeout) { | ||
clearTimeout(_presenceTimeout); | ||
_presenceTimeout = null; | ||
} | ||
_presenceTimeout = setTimeout(() => { | ||
send({ | ||
type: ClientMessageType.UpdateUserState, | ||
data: _toSend, | ||
}); | ||
_toSend = {}; | ||
_lastPresenceEmit = Date.now(); | ||
}, WAIT - (now - _lastPresenceEmit)); | ||
} | ||
function observeMyPresence(callback) { | ||
_listeners.onPresenceChange.push(callback); | ||
connect(); | ||
} | ||
function unobserveMyPresence(callback) { | ||
remove(_listeners.onPresenceChange, callback); | ||
disconnect(); | ||
} | ||
function observeOthersPresence(callback) { | ||
_listeners.onOthersPresenceChange.push(callback); | ||
connect(); | ||
} | ||
function unobserveOthersPresence(callback) { | ||
remove(_listeners.onOthersPresenceChange, callback); | ||
disconnect(); | ||
} | ||
return { | ||
///////////// | ||
// Storage // | ||
///////////// | ||
createRecord, | ||
createList, | ||
updateRecord(record, overrides) { | ||
updateDoc(_doc.updateRecord(record.id, overrides)); | ||
}, | ||
pushItem(list, item) { | ||
updateDoc(_doc.pushItem(list.id, item)); | ||
}, | ||
deleteItem(list, index) { | ||
updateDoc(_doc.deleteItem(list.id, index)); | ||
}, | ||
moveItem(list, index, targetIndex) { | ||
updateDoc(_doc.moveItem(list.id, index, targetIndex)); | ||
}, | ||
observeStorage: (callback, initialDocumentState) => { | ||
connect(() => { | ||
_initialRootFactory = initialDocumentState; | ||
send({ type: ClientMessageType.GetDocument }); | ||
}); | ||
_listeners.onRootChange.push(callback); | ||
}, | ||
unobserveStorage: (callback) => { | ||
remove(_listeners.onRootChange, callback); | ||
disconnect(); | ||
}, | ||
////////////// | ||
// Presence // | ||
////////////// | ||
getPresence, | ||
setPresence, | ||
observeMyPresence, | ||
unobserveMyPresence, | ||
observeOthersPresence, | ||
unobserveOthersPresence, | ||
addEventListener, | ||
removeEventListener, | ||
getRoom, | ||
}; | ||
} | ||
var ServerMessageType; | ||
(function (ServerMessageType) { | ||
ServerMessageType[ServerMessageType["UpdateUserState"] = 100] = "UpdateUserState"; | ||
ServerMessageType[ServerMessageType["UserJoined"] = 101] = "UserJoined"; | ||
ServerMessageType[ServerMessageType["UserLeft"] = 102] = "UserLeft"; | ||
ServerMessageType[ServerMessageType["InitialDocumentState"] = 200] = "InitialDocumentState"; | ||
ServerMessageType[ServerMessageType["DocumentOperations"] = 201] = "DocumentOperations"; | ||
})(ServerMessageType || (ServerMessageType = {})); | ||
var ClientMessageType; | ||
(function (ClientMessageType) { | ||
ClientMessageType[ClientMessageType["UpdateUserState"] = 100] = "UpdateUserState"; | ||
ClientMessageType[ClientMessageType["GetDocument"] = 200] = "GetDocument"; | ||
ClientMessageType[ClientMessageType["UpdateDocument"] = 201] = "UpdateDocument"; | ||
})(ClientMessageType || (ClientMessageType = {})); |
@@ -378,3 +378,4 @@ import { compare, makePosition } from "./position"; | ||
if (key !== "id" && key !== "type") { | ||
serializedData[key] = serialize(record[key]); | ||
const value = record[key]; // TODO: Find out why typescript does not like that | ||
serializedData[key] = serialize(value); | ||
} | ||
@@ -381,0 +382,0 @@ } |
export type { Record, RecordData, List } from "./doc"; | ||
export type { Users, Client, Room, UserState, InitialRootFactory, } from "./client"; | ||
export { createClient } from "./client"; | ||
export { RoomState } from "./types"; | ||
export type { Others, Presence, Room, InitialStorageFactory, Client, } from "./types"; |
export { createClient } from "./client"; | ||
export { RoomState } from "./types"; |
{ | ||
"name": "@liveblocks/client", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "", | ||
@@ -22,3 +22,3 @@ "main": "./lib/cjs/index.js", | ||
"@babel/preset-typescript": "^7.12.16", | ||
"@types/jest": "^26.0.20", | ||
"@types/jest": "^26.0.21", | ||
"babel-jest": "^26.6.3", | ||
@@ -25,0 +25,0 @@ "jest": "^26.6.3", |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
88431
29
2630
2