Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

@roomservice/browser

Package Overview
Dependencies
Maintainers
1
Versions
126
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@roomservice/browser - npm Package Compare versions

Comparing version 0.0.16 to 0.1.0

11

dist/client.d.ts
import Automerge from "automerge";
import { KeyValueObject } from "./types";
declare class RoomClient<T extends KeyValueObject> {
private _automergeConn;
private _docs;
private _peer;
private _socket?;

@@ -10,2 +9,3 @@ private _roomId?;

private readonly _authorizationUrl;
private _doc?;
private _socketURL;

@@ -17,2 +17,3 @@ private _onUpdateSocketCallback?;

constructor(authorizationUrl: string, reference: string, state?: T);
private init;
/**

@@ -26,3 +27,3 @@ * Manually attempt to restore the state from offline storage.

connect(): Promise<{
state: T | Automerge.FreezeObject<T>;
state: T;
reference: string;

@@ -37,6 +38,6 @@ }>;

onDisconnect(callback: () => any): void;
state(): Promise<Automerge.FreezeObject<T>>;
state(): Promise<Automerge.FreezeObject<T> | undefined>;
private syncOfflineCache;
private _sendMsgToSocket;
publishState(callback: (state: T) => void): T;
publishState(callback: (state: T) => void): Promise<T>;
}

@@ -43,0 +44,0 @@ export default class RoomServiceClient {

@@ -5,4 +5,6 @@ "use strict";

const automerge_1 = tslib_1.__importStar(require("automerge"));
const immutable_1 = require("immutable");
const invariant_1 = tslib_1.__importDefault(require("invariant"));
const lodash_1 = require("lodash");
const manymerge_1 = require("manymerge");
const safe_json_stringify_1 = tslib_1.__importDefault(require("safe-json-stringify"));

@@ -27,5 +29,6 @@ const authorize_1 = tslib_1.__importDefault(require("./authorize"));

this._sendMsgToSocket = (automergeMsg) => {
// Note that this._automergeConn.open() must be called after the socket
// definition
invariant_1.default(!!this._socket, "Expected this._socket to be defined. This is a sign of a broken client, if you're seeing this, please contact us.");
// we're offline, so don't do anything
if (!this._socket) {
return;
}
invariant_1.default(this._roomId, "Expected a _roomId to exist when publishing. This is a sign of a broken client, if you're seeing this, please contact us.");

@@ -44,18 +47,27 @@ const room = {

this._authorizationUrl = authorizationUrl;
// Automerge technically supports sending multiple docs
// over the wire at the same time, but for simplicity's sake
// we just use one doc at for the moment.
//
// In the future, we may support multiple documents per room.
const defaultDoc = automerge_1.default.from(state || {});
this._docs = new automerge_1.default.DocSet();
this._docs.setDoc("default", defaultDoc);
this._automergeConn = new automerge_1.default.Connection(this._docs, this._sendMsgToSocket);
this._peer = new manymerge_1.Peer(this._sendMsgToSocket);
// Whenever possible, we try to use the actorId defined in storage
this.init(state);
// We define this here so we can debounce the save function
// Otherwise we'll get quite the performance hit
let saveOffline = (docId, doc) => {
offline_1.default.set(this._reference, docId, automerge_1.save(doc));
offline_1.default.setDoc(this._reference, docId, automerge_1.save(doc));
};
this._saveOffline = lodash_1.debounce(saveOffline, 120);
}
async init(state) {
if (this._doc) {
return this._doc;
}
const actorId = await offline_1.default.getOrCreateActor();
const defaultDoc = automerge_1.default.from(state || {}, { actorId });
// Automerge technically supports sending multiple docs
// over the wire at the same time, but for simplicity's sake
// we just use one doc at for the moment.
//
// In the future, we may support multiple documents per room.
this._doc = defaultDoc;
this._peer.notify(this._doc);
return this._doc;
}
/**

@@ -73,2 +85,5 @@ * Manually attempt to restore the state from offline storage.

let session;
if (!this._doc) {
await this.init();
}
try {

@@ -83,3 +98,3 @@ const params = await authorize_1.default(this._authorizationUrl, this._reference);

return {
state: this._docs.getDoc("default"),
state: this._doc,
reference: this._reference

@@ -107,5 +122,11 @@ };

socket_1.default.on(this._socket, "connect", () => {
this._automergeConn.open();
this._peer.notify(this._doc);
this.syncOfflineCache();
});
// Required disconnect handler
socket_1.default.on(this._socket, "disconnect", reason => {
if (reason === "io server disconnect") {
console.warn("The RoomService client was forcibly disconnected from the server, likely due to invalid auth.");
}
});
/**

@@ -127,6 +148,10 @@ * We don't require these to be defined before hand since they're

try {
// NOTE: we purposefully don't define an actor id,
// since it's not assumed this state is defined by our actor.
state = automerge_1.default.load(room.state);
const local = await this.syncOfflineCache();
state = automerge_1.merge(local, state);
this._docs.setDoc("default", state);
// @ts-ignore no trust me I swear
this._doc = state;
this._peer.notify(this._doc);
}

@@ -153,3 +178,3 @@ catch (err) {

invariant_1.default(!this._onUpdateSocketCallback, "It looks like you've called onUpdate multiple times. Since this can cause quite severe performance issues if used incorrectly, we're not currently supporting this behavior. If you've got a use-case we haven't thought of, file a github issue and we may change this.");
const socketCallback = (data) => {
const socketCallback = async (data) => {
const { meta, payload } = JSON.parse(data);

@@ -167,3 +192,9 @@ if (!this._roomId) {

}
const newDoc = this._automergeConn.receiveMsg(payload.msg);
// This is effectively impossible tbh, but we like to be cautious
if (!this._doc) {
await this.init();
}
// convert the payload clock to a map
payload.msg.clock = immutable_1.Map(payload.msg.clock);
const newDoc = this._peer.applyMessage(payload.msg, this._doc);
// Automerge, in it's infinite wisdom, will just return undefined

@@ -176,4 +207,5 @@ // if a message is corrupted in some way that it doesn't like.

}
this._saveOffline("default", newDoc);
callback(newDoc);
this._doc = newDoc;
this._saveOffline("default", this._doc);
callback(this._doc);
};

@@ -204,33 +236,29 @@ // If we're offline, just wait till we're back online to assign this callback

async state() {
return this._docs.getDoc("default");
if (!this._doc) {
await this.init();
}
return this._doc;
}
async syncOfflineCache() {
const data = await offline_1.default.get(this._reference, "default");
const data = await offline_1.default.getDoc(this._reference, "default");
if (!data) {
return this._docs.getDoc("default");
return this._doc;
}
const offlineDoc = automerge_1.load(data);
const inMemDoc = this._docs.getDoc("default");
// Merge the offline doc with the current in-memory doc
let newDoc;
if (inMemDoc) {
newDoc = automerge_1.merge(inMemDoc, offlineDoc);
// We explictly do not add
const offlineDoc = automerge_1.load(data, {
actorId: await offline_1.default.getOrCreateActor()
});
this._doc = offlineDoc;
this._peer.notify(this._doc);
return offlineDoc;
}
async publishState(callback) {
let newDoc = automerge_1.default.change(this._doc, callback);
if (!newDoc) {
// this happens if someone deletes the doc, so we should just reinit it.
newDoc = await this.init();
}
else {
newDoc = offlineDoc;
}
this._docs.setDoc("default", newDoc);
return newDoc;
}
publishState(callback) {
const newDoc = automerge_1.default.change(this._docs.getDoc("default"), callback);
// Through a series of Automerge magic watchers, this call
// publishes the document to socket.io if we're connected.
//
// setDoc
// => Automerge.DocSet fires handler set in...
// => Automerge.Connection fires handler set in...
// => this._sendMsgToSocket()
this._docs.setDoc("default", newDoc);
this._doc = newDoc;
this._saveOffline("default", newDoc);
this._peer.notify(newDoc);
return newDoc;

@@ -237,0 +265,0 @@ }

@@ -57,3 +57,3 @@ "use strict";

sockets.emit("connect");
const newState = room.publishState(prevState => {
const newState = await room.publishState(prevState => {
prevState.someOption = "hello!";

@@ -71,3 +71,3 @@ });

const room = client.room("my-room");
jest.spyOn(offline_1.default, "get").mockImplementation(async (ref, doc) => {
jest.spyOn(offline_1.default, "getDoc").mockImplementation(async (ref, doc) => {
return automerge_1.save(automerge_1.from({ name: "offlinedoc" }));

@@ -82,3 +82,3 @@ });

// setup offline
jest.spyOn(offline_1.default, "get").mockImplementation(async (ref, doc) => {
jest.spyOn(offline_1.default, "getDoc").mockImplementation(async (ref, doc) => {
return automerge_1.save(automerge_1.from({ offline: "offline" }));

@@ -106,3 +106,3 @@ });

room._roomId = "my-room-id";
const setOffline = jest.spyOn(offline_1.default, "set");
const setOffline = jest.spyOn(offline_1.default, "setDoc");
onUpdateSocket(JSON.stringify({

@@ -109,0 +109,0 @@ meta: {

@@ -7,6 +7,7 @@ /**

interface IOffline {
get: (roomRef: string, docId: string) => Promise<string>;
set: (roomRef: string, docId: string, value: string) => Promise<any>;
getDoc: (roomRef: string, docId: string) => Promise<string>;
setDoc: (roomRef: string, docId: string, value: string) => Promise<any>;
getOrCreateActor: () => Promise<string>;
}
declare const Offline: IOffline;
export default Offline;

@@ -8,8 +8,19 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const idb_keyval_1 = require("idb-keyval");
const v4_1 = tslib_1.__importDefault(require("uuid/v4"));
const Offline = {
get: (roomRef, docId) => idb_keyval_1.get("rs:" + roomRef + "/" + docId),
set: (roomRef, docId, value) => idb_keyval_1.set("rs:" + roomRef + "/" + docId, value)
getDoc: (roomRef, docId) => idb_keyval_1.get("rs:" + roomRef + "/" + docId),
setDoc: (roomRef, docId, value) => idb_keyval_1.set("rs:" + roomRef + "/" + docId, value),
getOrCreateActor: async () => {
const actor = await idb_keyval_1.get("rs:actor");
if (actor) {
return actor;
}
const id = v4_1.default();
idb_keyval_1.set("rs:actor", id);
return id;
}
};
exports.default = Offline;
//# sourceMappingURL=offline.js.map
/// <reference types="socket.io-client" />
declare const Sockets: {
newSocket(url: string, opts: SocketIOClient.ConnectOpts): SocketIOClient.Socket;
on(socket: SocketIOClient.Socket, event: "error" | "connect" | "disconnect" | "sync_room_state", fn: (...args: any[]) => void): void;
on(socket: SocketIOClient.Socket, event: "error" | "disconnect" | "connect" | "sync_room_state", fn: (...args: any[]) => void): void;
emit(socket: SocketIOClient.Socket, event: "sync_room_state", ...args: any[]): void;

@@ -6,0 +6,0 @@ disconnect(socket: SocketIOClient.Socket): void;

{
"name": "@roomservice/browser",
"version": "0.0.16",
"version": "0.1.0",
"main": "dist/index",

@@ -23,2 +23,3 @@ "types": "dist/index",

"@types/socket.io-client": "^1.4.32",
"@types/uuid": "^3.4.6",
"jest": "^24.9.0",

@@ -32,2 +33,3 @@ "nock": "^11.7.0",

"idb-keyval": "^3.2.0",
"immutable": "^4.0.0-rc.12",
"invariant": "^2.2.4",

@@ -37,5 +39,7 @@ "ky": "^0.16.1",

"lodash": "^4.17.15",
"manymerge": "2.2.0",
"safe-json-stringify": "^1.2.0",
"socket.io-client": "^2.3.0"
"socket.io-client": "^2.3.0",
"uuid": "^3.3.3"
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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