@roomservice/browser
Advanced tools
Comparing version 0.8.0 to 1.0.0-1
@@ -7,149 +7,119 @@ 'use strict'; | ||
var Automerge = require('automerge'); | ||
var Automerge__default = _interopDefault(Automerge); | ||
var immutable = require('immutable'); | ||
var invariant = _interopDefault(require('invariant')); | ||
var lodash = require('lodash'); | ||
var manymerge = require('manymerge'); | ||
var safeJsonStringify = _interopDefault(require('safe-json-stringify')); | ||
var idbKeyval = require('idb-keyval'); | ||
var uuid = _interopDefault(require('uuid/v4')); | ||
var IO = _interopDefault(require('socket.io-client')); | ||
var ky = _interopDefault(require('ky-universal')); | ||
var invariant = _interopDefault(require('tiny-invariant')); | ||
function _defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
// export const WS_URL = 'ws://localhost:3452'; | ||
// export const DOCS_URL = 'http://localhost:3454'; | ||
var WS_URL = 'wss://super.roomservice.dev/ws'; | ||
var DOCS_URL = 'https://super.roomservice.dev'; | ||
function _createClass(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
var SuperlumeWebSocket = /*#__PURE__*/function () { | ||
function SuperlumeWebSocket(conn) { | ||
var _this = this; | ||
// A type of promise-like that resolves synchronously and supports only one observer | ||
this.callbacks = {}; | ||
this.lastTime = 0; | ||
this.msgsThisMilisecond = 0; | ||
this.conn = conn; | ||
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator"; | ||
this.conn.onmessage = function (ev) { | ||
var msg = JSON.parse(ev.data); | ||
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator"; | ||
_this.dispatch(msg.type, msg.body); | ||
}; | ||
} | ||
// Asynchronously call a function and send errors to recovery continuation | ||
function _catch(body, recover) { | ||
try { | ||
var result = body(); | ||
} catch(e) { | ||
return recover(e); | ||
} | ||
if (result && result.then) { | ||
return result.then(void 0, recover); | ||
} | ||
return result; | ||
} | ||
var _proto = SuperlumeWebSocket.prototype; | ||
var ROOM_SERICE_CLIENT_URL = 'https://aws.roomservice.dev'; | ||
_proto.timestamp = function timestamp() { | ||
var time = Date.now(); | ||
var Offline = { | ||
getDoc: function (roomRef, docId) { | ||
try { | ||
return Promise.resolve(_catch(function () { | ||
return Promise.resolve(idbKeyval.get('rs:' + roomRef + '/' + docId)); | ||
}, function (err) { | ||
console.warn("Something went wrong getting Room Service's state offline", err); | ||
return ''; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
if (time === this.lastTime) { | ||
this.msgsThisMilisecond++; | ||
} else { | ||
this.lastTime = time; | ||
this.msgsThisMilisecond = 0; | ||
} | ||
}, | ||
setDoc: function (roomRef, docId, value) { | ||
try { | ||
var _temp2 = _catch(function () { | ||
return Promise.resolve(idbKeyval.set('rs:' + roomRef + '/' + docId, value)).then(function () {}); | ||
}, function (err) { | ||
console.warn("Something went wrong saving Room Service's state offline", err); | ||
}); | ||
return Promise.resolve(_temp2 && _temp2.then ? _temp2.then(function () {}) : void 0); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}, | ||
getOrCreateActor: function () { | ||
try { | ||
!(typeof window !== 'undefined') ? "development" !== "production" ? invariant(false, "getOrCreateActor was used on the server side; this is a bug in the client, if you're seeing this, let us know.") : invariant(false) : void 0; | ||
return Promise.resolve(_catch(function () { | ||
return Promise.resolve(idbKeyval.get('rs:actor')).then(function (actor) { | ||
if (actor) { | ||
return actor; | ||
} | ||
return time + ":" + this.msgsThisMilisecond; | ||
}; | ||
var id = uuid(); | ||
idbKeyval.set('rs:actor', id); | ||
return id; | ||
}); | ||
}, function () { | ||
console.warn('Cant use offline mode in this environment, skipping.'); | ||
return null; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
}; | ||
_proto.send = function send(msgType, body) { | ||
var ts = this.timestamp(); | ||
var msg = { | ||
type: msgType, | ||
ts: ts, | ||
ver: 0, | ||
body: body | ||
}; | ||
this.conn.send(JSON.stringify(msg)); | ||
}; | ||
/** | ||
* This is just a wrapper around Socket.io that's easier | ||
* to test. | ||
*/ | ||
_proto.bind = function bind(msgType, callback) { | ||
this.callbacks[msgType] = this.callbacks[msgType] || []; | ||
this.callbacks[msgType].push(callback); | ||
return callback; | ||
}; | ||
var Sockets = { | ||
newSocket: function newSocket(url, opts) { | ||
return IO(url, opts); | ||
}, | ||
on: function on(socket, event, fn) { | ||
!(!!socket && !!event) ? invariant(false, 'Requires socket defined') : void 0; | ||
socket.on(event, fn); | ||
}, | ||
off: function off(socket, event) { | ||
socket.off(event); | ||
}, | ||
emit: function emit(socket, event) { | ||
!(!!socket && !!event) ? invariant(false, 'Requires socket defined') : void 0; | ||
_proto.unbind = function unbind(msgType, callback) { | ||
this.callbacks[msgType] = this.callbacks[msgType].filter(function (c) { | ||
return c !== callback; | ||
}); | ||
}; | ||
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | ||
args[_key - 2] = arguments[_key]; | ||
_proto.dispatch = function dispatch(msgType, body) { | ||
var stack = this.callbacks[msgType]; | ||
if (!stack) return; | ||
for (var i = 0; i < stack.length; i++) { | ||
stack[i](body); | ||
} | ||
}; | ||
socket.emit.apply(socket, [event].concat(args)); | ||
}, | ||
disconnect: function disconnect(socket) { | ||
socket.disconnect(); | ||
} | ||
}; | ||
return SuperlumeWebSocket; | ||
}(); | ||
var authorizeSocket = function authorizeSocket(socket, token, roomId) { | ||
var fetchSession = function fetchSession(url, room, document) { | ||
try { | ||
return Promise.resolve(new Promise(function (resolve) { | ||
!socket ? "development" !== "production" ? invariant(false, 'Requires socket to be defined') : invariant(false) : void 0; | ||
var timeout = setTimeout(function () { | ||
resolve(false); | ||
}, 15000); | ||
Sockets.emit(socket, 'authenticate', { | ||
meta: { | ||
roomId: roomId | ||
}, | ||
payload: token | ||
return Promise.resolve(fetch(url, { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
resources: [{ | ||
object: 'document', | ||
reference: document, | ||
permission: 'read_write' | ||
}, { | ||
object: 'room', | ||
reference: room, | ||
permission: 'join' | ||
}] | ||
}) | ||
})).then(function (res) { | ||
if (res.status === 401) { | ||
// Todo, make a better path for handling this | ||
throw new Error('AuthURL returned unauthorized'); | ||
} | ||
return Promise.resolve(res.json()).then(function (_ref) { | ||
var token = _ref.token, | ||
guestID = _ref.guest_id, | ||
resources = _ref.resources; | ||
if (!resources || !token || !guestID) { | ||
throw new Error('Invalid response from the AuthURL: ' + url); | ||
} | ||
var docID = resources.find(function (r) { | ||
return r.object === 'document'; | ||
}).id; | ||
var roomID = resources.find(function (r) { | ||
return r.object === 'room'; | ||
}).id; | ||
return { | ||
token: token, | ||
guestID: guestID, | ||
docID: docID, | ||
roomID: roomID | ||
}; | ||
}); | ||
Sockets.on(socket, 'authenticated', function () { | ||
clearTimeout(timeout); | ||
Sockets.off(socket, 'authenticated'); | ||
resolve(true); | ||
}); | ||
})); | ||
}); | ||
} catch (e) { | ||
@@ -159,676 +129,552 @@ return Promise.reject(e); | ||
}; | ||
var fetchDocument = function fetchDocument(url, token, docID) { | ||
try { | ||
return Promise.resolve(fetch(url + '/' + docID, { | ||
headers: { | ||
Authorization: 'Bearer: ' + token | ||
} | ||
})).then(function (res) { | ||
return Promise.resolve(res.json()); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var DOC_NAMESPACE = '/v1/doc'; | ||
function _defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
function asRoomStr(room) { | ||
return safeJsonStringify(room); | ||
function _createClass(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
var DocClient = /*#__PURE__*/function () { | ||
function DocClient(parameters) { | ||
var _this2 = this; | ||
function _unsupportedIterableToArray(o, minLen) { | ||
if (!o) return; | ||
if (typeof o === "string") return _arrayLikeToArray(o, minLen); | ||
var n = Object.prototype.toString.call(o).slice(8, -1); | ||
if (n === "Object" && o.constructor) n = o.constructor.name; | ||
if (n === "Map" || n === "Set") return Array.from(o); | ||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); | ||
} | ||
var _this = this; | ||
function _arrayLikeToArray(arr, len) { | ||
if (len == null || len > arr.length) len = arr.length; | ||
// The manymerge client will call this function when it picks up changes. | ||
// | ||
// WARNING: This function is an arrow function specifically because | ||
// it needs to access this._socket. If you use a regular function, | ||
// it won't work. | ||
this._sendMsgToSocket = function (automergeMsg) { | ||
try { | ||
// we're offline, so don't do anything | ||
if (!_this._socket) { | ||
return Promise.resolve(); | ||
} | ||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; | ||
return Promise.resolve(_this._authorized).then(function (isAuthorized) { | ||
if (!isAuthorized) { | ||
console.error('Room Service is unable to authorize'); | ||
return; | ||
} | ||
return arr2; | ||
} | ||
!_this._roomId ? "development" !== "production" ? invariant(false, "Expected a _roomId to exist when publishing. This is a sign of a broken client, if you're seeing this, please contact us.") : invariant(false) : void 0; | ||
var room = { | ||
meta: { | ||
roomId: _this._roomId | ||
}, | ||
payload: { | ||
msg: automergeMsg | ||
} | ||
}; | ||
Sockets.emit(_this._socket, 'sync_room_state', asRoomStr(room)); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
function _createForOfIteratorHelperLoose(o, allowArrayLike) { | ||
var it; | ||
this._roomReference = parameters.roomReference; | ||
this._defaultDoc = parameters.defaultDoc; | ||
this._peer = new manymerge.Peer(this._sendMsgToSocket); | ||
this._socketURL = ROOM_SERICE_CLIENT_URL; // We define this here so we can debounce the save function | ||
// Otherwise we'll get quite the performance hit | ||
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { | ||
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { | ||
if (it) o = it; | ||
var i = 0; | ||
return function () { | ||
if (i >= o.length) return { | ||
done: true | ||
}; | ||
return { | ||
done: false, | ||
value: o[i++] | ||
}; | ||
}; | ||
} | ||
var saveOffline = function saveOffline(docId, doc) { | ||
Offline.setDoc(_this2._roomReference, docId, Automerge.save(doc)); | ||
}; | ||
this._saveOffline = lodash.debounce(saveOffline, 120); | ||
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); | ||
} | ||
var _proto = DocClient.prototype; | ||
it = o[Symbol.iterator](); | ||
return it.next.bind(it); | ||
} | ||
_proto.readActorIdThenCreateDoc = function readActorIdThenCreateDoc(state) { | ||
try { | ||
var _this4 = this; | ||
function unescapeID(checkpoint, id) { | ||
if (id === 'root') return 'root'; | ||
return Promise.resolve(Offline.getOrCreateActor()).then(function (actorId) { | ||
_this4._actorId = actorId; | ||
return _this4.createDoc(actorId, state); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var _id$split = id.split(':'), | ||
index = _id$split[0], | ||
a = _id$split[1]; | ||
_proto.createDoc = function createDoc(actorId, state) { | ||
if (this._doc) { | ||
return this._doc; | ||
} | ||
return index + ':' + checkpoint.actors[parseInt(a)]; | ||
} | ||
var params = actorId ? { | ||
actorId: actorId | ||
} : undefined; | ||
var defaultDoc = Automerge__default.from(state || {}, params); // 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. | ||
/** | ||
* A Reverse Tree is one where the children point to the | ||
* parents, instead of the otherway around. | ||
* | ||
* We use a reverse tree because the "insert" operation | ||
* can be done in paralell. | ||
*/ | ||
this._doc = defaultDoc; | ||
var ReverseTree = /*#__PURE__*/function () { | ||
function ReverseTree(actor) { | ||
// The number of operations used by this tree | ||
this.count = 0; | ||
this.actor = actor; | ||
this.nodes = {}; | ||
this.log = []; | ||
} | ||
this._peer.notify(this._doc); | ||
var _proto = ReverseTree.prototype; | ||
return this._doc; | ||
} | ||
/** | ||
* Manually attempt to restore the state from offline storage. | ||
*/ | ||
; | ||
_proto["import"] = function _import(checkpoint, listID) { | ||
!checkpoint ? invariant(false) : void 0; | ||
var list = checkpoint.lists[listID]; | ||
var afters = list.afters || []; | ||
var ids = list.ids || []; | ||
var values = list.values || []; // Rehydrate the cache | ||
_proto.restore = function restore() { | ||
try { | ||
var _temp3 = function _temp3() { | ||
return _this6.syncOfflineCache(); | ||
for (var i = 0; i < afters.length; i++) { | ||
var node = { | ||
after: unescapeID(checkpoint, afters[i]), | ||
id: unescapeID(checkpoint, ids[i]), | ||
value: values[i] | ||
}; | ||
this.nodes[node.id] = node; | ||
this.log.push(node); | ||
} | ||
var _this6 = this; | ||
this.count = this.log.length; | ||
}; | ||
// We can't restore on the server, or in environments | ||
// where indexedb is not defined | ||
if (typeof window === 'undefined' || typeof indexedDB === 'undefined') { | ||
return Promise.resolve({}); | ||
} | ||
var _temp4 = function () { | ||
if (!_this6._doc) { | ||
return Promise.resolve(_this6.readActorIdThenCreateDoc(_this6._defaultDoc)).then(function () {}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp4 && _temp4.then ? _temp4.then(_temp3) : _temp3(_temp4)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
_proto.get = function get(itemID) { | ||
if (this.nodes[itemID]) { | ||
return this.nodes[itemID].value; | ||
} | ||
} | ||
/** | ||
* Attempts to go online. | ||
*/ | ||
; | ||
_proto.init = function init(_ref) { | ||
var room = _ref.room, | ||
session = _ref.session; | ||
return undefined; | ||
}; | ||
try { | ||
var _temp11 = function _temp11() { | ||
var _exit = false; | ||
_proto.insert = function insert(after, value, externalNewID) { | ||
!this.log ? invariant(false) : void 0; | ||
var id = externalNewID; | ||
function _temp8(_result) { | ||
if (_exit) return _result; | ||
_this8._roomId = room.id; | ||
_this8._socket = Sockets.newSocket(_this8._socketURL + DOC_NAMESPACE, { | ||
transports: ['websocket'] | ||
}); | ||
Sockets.on(_this8._socket, 'reconnect_attempt', function () { | ||
!_this8._socket ? "development" !== "production" ? invariant(false) : invariant(false) : void 0; | ||
_this8._socket.io.opts.transports = ['websocket']; | ||
}); | ||
/** | ||
* Errors | ||
*/ | ||
if (!id) { | ||
id = this.count + ":" + this.actor; | ||
this.count++; | ||
} | ||
Sockets.on(_this8._socket, 'error', function (data) { | ||
try { | ||
var _JSON$parse = JSON.parse(data), | ||
message = _JSON$parse.message; | ||
var node = { | ||
after: after, | ||
value: value, | ||
id: id | ||
}; | ||
this.nodes[id] = node; | ||
this.log.push(node); | ||
return id; | ||
}; | ||
console.error("Error from Socket: " + message); | ||
} catch (err) { | ||
console.error("Unparsable error from socket: " + data); | ||
} | ||
}); // Immediately attempt to authorize via traditional auth | ||
_proto.put = function put(itemID, value) { | ||
if (!!this.nodes[itemID]) { | ||
this.nodes[itemID].value = value; | ||
} | ||
}; | ||
_this8._authorized = authorizeSocket(_this8._socket, session.token, room.id); // Required connect handler | ||
_proto.has = function has(itemID) { | ||
return !!this.nodes[itemID]; | ||
}; | ||
Sockets.on(_this8._socket, 'connect', function () { | ||
_this8._peer.notify(_this8._doc); | ||
_proto["delete"] = function _delete(itemID) { | ||
this.nodes[itemID].value = { | ||
t: '' | ||
}; | ||
}; | ||
_this8.syncOfflineCache(); | ||
}); // Required disconnect handler | ||
_proto.toTree = function toTree() { | ||
var root = { | ||
children: [], | ||
id: 'root', | ||
value: '' | ||
}; | ||
var trees = { | ||
root: root | ||
}; | ||
Sockets.on(_this8._socket, 'disconnect', function (reason) { | ||
if (reason === 'io server disconnect') { | ||
console.warn('The RoomService client was forcibly disconnected from the server, likely due to invalid auth.'); | ||
} | ||
}); | ||
/** | ||
* We don't require these to be defined before hand since they're | ||
* optional | ||
*/ | ||
for (var _iterator = _createForOfIteratorHelperLoose(this.log), _step; !(_step = _iterator()).done;) { | ||
var node = _step.value; | ||
var tree = { | ||
children: [], | ||
id: node.id, | ||
value: node.value | ||
}; | ||
trees[node.id] = tree; | ||
if (_this8._onUpdateSocketCallback) { | ||
Sockets.on(_this8._socket, 'sync_room_state', _this8._onUpdateSocketCallback); | ||
} | ||
if (node.after === 'root') { | ||
root.children.push(tree); | ||
} else { | ||
if (!trees[node.after]) { | ||
throw new Error("Unexpectedly missing node " + node.after); | ||
} | ||
if (_this8._onConnectSocketCallback) { | ||
Sockets.on(_this8._socket, 'connect', _this8._onConnectSocketCallback); | ||
} | ||
trees[node.after].children.push(tree); | ||
} | ||
} | ||
if (_this8._onDisconnectSocketCallback) { | ||
Sockets.on(_this8._socket, 'disconnect', _this8._onDisconnectSocketCallback); | ||
} // Load the document of the room. | ||
return root; | ||
}; | ||
_proto.sortLog = function sortLog() { | ||
this.log.sort(function (a, b) { | ||
var _a$id$split = a.id.split(':'), | ||
leftCount = _a$id$split[0], | ||
leftActor = _a$id$split[1]; | ||
return Promise.resolve(fetch(_this8._socketURL + ("/client/v1/rooms/" + room.id + "/documents/default"), { | ||
headers: { | ||
authorization: 'Bearer ' + session.token | ||
} | ||
})).then(function (result) { | ||
if (result.status !== 200) { | ||
throw new Error("Unexpectedly did not find document for room " + room.reference); | ||
} | ||
var _b$id$split = b.id.split(':'), | ||
rightCount = _b$id$split[0], | ||
rightActor = _b$id$split[1]; | ||
return Promise.resolve(result.text()).then(function (roomStateStr) { | ||
function _temp6() { | ||
return { | ||
doc: state | ||
}; | ||
} | ||
if (leftCount === rightCount) { | ||
return leftActor.localeCompare(rightActor); | ||
} | ||
// Merge RoomService's online cache with what we have locally | ||
var state; | ||
return parseInt(leftCount) - parseInt(rightCount); | ||
}); | ||
}; | ||
var _temp5 = _catch(function () { | ||
// NOTE: we purposefully don't define an actor id, | ||
// since it's not assumed this state is defined by our actor. | ||
state = Automerge__default.load(roomStateStr); | ||
return Promise.resolve(_this8.syncOfflineCache()).then(function (local) { | ||
state = Automerge.merge(local, state); | ||
_this8._doc = state; | ||
_proto.lastID = function lastID() { | ||
this.sortLog(); // -- Convert the log into a regular tree | ||
_this8._peer.notify(_this8._doc); | ||
}); | ||
}, function (err) { | ||
console.error(err); | ||
state = {}; | ||
}); | ||
var root = this.toTree(); // Search the left side of the tree | ||
return _temp5 && _temp5.then ? _temp5.then(_temp6) : _temp6(_temp5); | ||
}); | ||
}); | ||
} | ||
var _temp7 = function () { | ||
if (!room || !session) { | ||
return Promise.resolve(_this8.syncOfflineCache()).then(function () { | ||
_exit = true; | ||
return { | ||
doc: _this8._doc | ||
}; | ||
}); | ||
} | ||
}(); | ||
// we're offline, so we should just continue with our fun little world | ||
return _temp7 && _temp7.then ? _temp7.then(_temp8) : _temp8(_temp7); | ||
}; | ||
var _this8 = this; | ||
if (typeof window === 'undefined') { | ||
return Promise.resolve({ | ||
doc: undefined | ||
}); | ||
function left(t) { | ||
if (!t.children || t.children.length === 0) { | ||
return t; | ||
} | ||
var _temp12 = function () { | ||
if (!_this8._doc) { | ||
return Promise.resolve(_this8.readActorIdThenCreateDoc(_this8._defaultDoc)).then(function () {}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp12 && _temp12.then ? _temp12.then(_temp11) : _temp11(_temp12)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
return left(t.children[0]); | ||
} | ||
} | ||
/** | ||
* Manually go offline | ||
*/ | ||
; | ||
_proto.disconnect = function disconnect() { | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call disconnect on the server, this is a no-op.'); | ||
return; | ||
} | ||
if (this._socket) { | ||
Sockets.disconnect(this._socket); | ||
} | ||
this._socket = undefined; | ||
return left(root).id; | ||
}; | ||
_proto.onSetDoc = function onSetDoc(callback) { | ||
var _this9 = this; | ||
_proto.toArray = function toArray() { | ||
this.sortLog(); // -- Convert the log into a regular tree | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call onSetDoc on the server, this is a no-op.'); | ||
return; | ||
} | ||
var root = this.toTree(); // -- Do a depth-first traversal to get the result | ||
!!this._onUpdateSocketCallback ? invariant(false, "It looks like you've called onSetDoc 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.") : void 0; | ||
function postorder(t) { | ||
if (!t.children || t.children.length === 0) { | ||
return []; | ||
} | ||
var socketCallback = function socketCallback(data) { | ||
try { | ||
var _temp15 = function _temp15() { | ||
// convert the payload clock to a map | ||
payload.msg.clock = immutable.Map(payload.msg.clock); | ||
var vals = []; | ||
try { | ||
var newDoc = _this9._peer.applyMessage(payload.msg, _this9._doc); // if we don't have any new changes, we don't need to do anything. | ||
for (var _iterator2 = _createForOfIteratorHelperLoose(t.children), _step2; !(_step2 = _iterator2()).done;) { | ||
var child = _step2.value; | ||
var value = child.value; | ||
if (typeof child.value !== 'string') { | ||
// Skip tombstones | ||
if (child.value.t === '') { | ||
vals = vals.concat([].concat(postorder(child))); | ||
continue; | ||
} | ||
if (!newDoc) { | ||
return; | ||
} | ||
throw new Error('Unimplemented'); | ||
} | ||
_this9._doc = newDoc; | ||
vals = vals.concat([value].concat(postorder(child))); | ||
} | ||
_this9._saveOffline('default', _this9._doc); // From a user's perspective, the document should only update | ||
// if we've actually made changes (since only we care about the | ||
// clock position of everyone else). | ||
return vals; | ||
} | ||
return postorder(root); | ||
}; | ||
if (payload.msg.changes) { | ||
callback(_this9._doc); | ||
} | ||
} catch (err) { | ||
// Ignore Automerge double-apply errors | ||
if (err.message && err.message.includes('Inconsistent reuse of sequence number')) { | ||
return; | ||
} | ||
_createClass(ReverseTree, [{ | ||
key: "length", | ||
get: function get() { | ||
return Object.keys(this.nodes).length; | ||
} | ||
}]); | ||
console.error(err); | ||
} | ||
}; | ||
return ReverseTree; | ||
}(); | ||
var _JSON$parse2 = JSON.parse(data), | ||
meta = _JSON$parse2.meta, | ||
payload = _JSON$parse2.payload; | ||
function escape(value) { | ||
if (typeof value === 'number') return "" + value;else return "\"" + value + "\""; | ||
} | ||
function unescape$1(value) { | ||
if (value.length >= 2 && value[0] === '"' && value[value.length - 1] === '"') { | ||
return value; | ||
} | ||
if (!_this9._roomId) { | ||
throw new Error("Expected a _roomId to be defined before we invoked the the onSetDoc callback. This is a sign of a broken client, please contact us if you're seeing this."); | ||
} // This socket event will fire for ALL rooms, so we need to check | ||
// if this callback refers to this particular room. | ||
return parseInt(value); | ||
} | ||
var ListClient = /*#__PURE__*/function () { | ||
function ListClient(checkpoint, roomID, docID, listID, ws, actor) { | ||
// Map indexes to item ids | ||
this.itemIDs = []; | ||
this.roomID = roomID; | ||
this.docID = docID; | ||
this.id = listID; | ||
this.ws = ws; | ||
this.rt = new ReverseTree(actor); | ||
this.rt["import"](checkpoint, listID); | ||
var list = checkpoint.lists[listID]; | ||
var ids = list.ids || []; | ||
if (meta.roomId !== _this9._roomId) { | ||
return Promise.resolve(); | ||
} | ||
for (var i = 0; i < ids.length; i++) { | ||
this.itemIDs.push(unescapeID(checkpoint, ids[i])); | ||
} | ||
} | ||
if (!payload.msg) { | ||
throw new Error("The room's state object does not include an 'msg' attribute, which could signal a corrupted room. If you're seeing this in production, that's quite bad and represents a fixable bug within the SDK itself. Please let us know and we'll fix it immediately!"); | ||
} // This is effectively impossible tbh, but we like to be cautious | ||
var _proto = ListClient.prototype; | ||
_proto.sendCmd = function sendCmd(cmd) { | ||
this.ws.send('doc:cmd', { | ||
room: this.roomID, | ||
args: cmd | ||
}); | ||
}; | ||
var _temp16 = function () { | ||
if (!_this9._doc) { | ||
return Promise.resolve(_this9.readActorIdThenCreateDoc(_this9._defaultDoc)).then(function () {}); | ||
} | ||
}(); | ||
_proto.clone = function clone() { | ||
return Object.assign(Object.create(Object.getPrototypeOf(this)), this); | ||
}; | ||
return Promise.resolve(_temp16 && _temp16.then ? _temp16.then(_temp15) : _temp15(_temp16)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; // If we're offline, just wait till we're back online to assign this callback | ||
_proto.update = function update(cmd) { | ||
if (cmd.length < 3) { | ||
throw new Error('Unexpected command: ' + cmd); | ||
} | ||
var keyword = cmd[0]; | ||
var docID = cmd[1]; | ||
var id = cmd[2]; | ||
if (!this._socket) { | ||
this._onUpdateSocketCallback = socketCallback; | ||
return; | ||
if (docID !== this.docID || id !== this.id) { | ||
throw new Error('Command unexpectedly routed to the wrong client'); | ||
} | ||
Sockets.on(this._socket, 'sync_room_state', socketCallback); | ||
}; | ||
switch (keyword) { | ||
case 'lins': | ||
var insAfter = cmd[3]; | ||
var insItemID = cmd[4]; | ||
var insValue = cmd[5]; | ||
this.rt.insert(insAfter, insValue, insItemID); | ||
break; | ||
_proto.onConnect = function onConnect(callback) { | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call onConnect on the server, this is a no-op.'); | ||
return; | ||
} // If we're offline, cue this up for later. | ||
case 'lput': | ||
var putItemID = cmd[3]; | ||
var putVal = cmd[4]; | ||
this.rt.put(putItemID, putVal); | ||
break; | ||
case 'ldel': | ||
var delItemID = cmd[3]; | ||
this.rt["delete"](delItemID); | ||
break; | ||
if (!this._socket) { | ||
this._onConnectSocketCallback = callback; | ||
return; | ||
default: | ||
throw new Error('Unexpected command keyword: ' + keyword); | ||
} | ||
this._socket.on('connect', callback); | ||
return this.clone(); | ||
}; | ||
_proto.onDisconnect = function onDisconnect(callback) { | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call onDisconnect on the server, this is a no-op.'); | ||
return; | ||
} // If we're offline, cue this up for later. | ||
_proto.get = function get(index) { | ||
var itemID = this.itemIDs[index]; | ||
if (!itemID) return undefined; | ||
var val = this.rt.get(itemID); | ||
if (!val) return undefined; | ||
if (typeof val === 'object') { | ||
if (val.t === '') { | ||
return undefined; | ||
} | ||
if (!this._socket) { | ||
this._onDisconnectSocketCallback = callback; | ||
return; | ||
throw new Error('Unimplemented references'); | ||
} | ||
this._socket.on('disconnect', callback); | ||
return unescape$1(val); | ||
}; | ||
_proto.syncOfflineCache = function syncOfflineCache() { | ||
try { | ||
var _this11 = this; | ||
_proto.set = function set(index, val) { | ||
var itemID = this.itemIDs[index]; | ||
return Promise.resolve(Offline.getDoc(_this11._roomReference, 'default')).then(function (data) { | ||
return data ? Promise.resolve(Offline.getOrCreateActor()).then(function (actorId) { | ||
if (!actorId) { | ||
console.error("Unexpectedly didn't find offline support in an environment like a browser where we should have offline support."); | ||
} // We explictly do not add | ||
if (!itemID) { | ||
throw new Error('Unexpected'); | ||
} | ||
var escaped = escape(val); // Local | ||
var offlineDoc = Automerge.load(data, { | ||
actorId: actorId | ||
}); | ||
_this11._doc = offlineDoc; | ||
this.rt.put(itemID, escaped); // Remote | ||
_this11._peer.notify(_this11._doc); | ||
return offlineDoc; | ||
}) : _this11._doc; | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
this.sendCmd(['lput', this.docID, this.id, itemID, escaped]); | ||
return this.clone(); | ||
}; | ||
_proto.setDoc = function setDoc(callback) { | ||
try { | ||
var _temp19 = function _temp19() { | ||
if (typeof callback !== 'function') { | ||
throw new Error("room.publishDoc expects a function."); | ||
} | ||
_proto["delete"] = function _delete(index) { | ||
var itemID = this.itemIDs[index]; | ||
if (!itemID) return Object.assign({}, this); // Local | ||
var newDoc = Automerge__default.change(_this13._doc, callback); | ||
this.rt["delete"](itemID); // Remote | ||
if (!newDoc) { | ||
!!!_this13._actorId ? "development" !== "production" ? invariant(false, "The client is trying to regenerate a deleted document, but isn't able to access the cached actor id. This is probably a bug in the client, if you see this, we're incredibly sorry! Please let us know. In the meantime, you may be able work around this by ensuring 'await room.restore()' has finished before calling 'publishState'.") : invariant(false) : void 0; // this happens if someone deletes the doc, so we should just reinit it. | ||
this.sendCmd(['ldel', this.docID, this.id, itemID]); | ||
Object.assign(Object.create(Object.getPrototypeOf(this)), this); | ||
return this.clone(); | ||
}; | ||
newDoc = _this13.createDoc(_this13._actorId, _this13._defaultDoc); | ||
} | ||
_proto.insertAfter = function insertAfter(index, val) { | ||
var afterID = this.itemIDs[index]; | ||
_this13._doc = newDoc; | ||
if (!afterID) { | ||
throw new RangeError("List '" + this.id + "' has no index: '" + index + "'"); | ||
} | ||
_this13._saveOffline('default', newDoc); | ||
var escaped = escape(val); // Local | ||
_this13._peer.notify(newDoc); | ||
var itemID = this.rt.insert(afterID, escaped); | ||
this.itemIDs.splice(index, 0, itemID); // Remote | ||
return newDoc; | ||
}; | ||
this.sendCmd(['lins', this.docID, this.id, afterID, itemID, escaped]); | ||
return this.clone(); | ||
}; | ||
var _this13 = this; | ||
_proto.push = function push(val) { | ||
var lastID = 'root'; | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call setDoc on the server, this is a no-op.'); | ||
return Promise.resolve({}); | ||
} | ||
var _temp20 = function () { | ||
if (!_this13._doc) { | ||
return Promise.resolve(_this13.readActorIdThenCreateDoc(_this13._defaultDoc)).then(function (_this12$readActorIdTh) { | ||
_this13._doc = _this12$readActorIdTh; | ||
}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp20 && _temp20.then ? _temp20.then(_temp19) : _temp19(_temp20)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
if (this.itemIDs.length !== 0) { | ||
lastID = this.itemIDs[this.itemIDs.length - 1]; | ||
} | ||
}; | ||
_proto.undo = function undo() { | ||
if (this._doc && Automerge__default.canUndo(this._doc)) { | ||
var newDoc = Automerge__default.undo(this._doc); | ||
this._doc = newDoc; | ||
var escaped = escape(val); // Local | ||
this._saveOffline('default', newDoc); | ||
var itemID = this.rt.insert(lastID, escaped); | ||
this.itemIDs.push(itemID); // Remote | ||
this._peer.notify(newDoc); | ||
return newDoc; | ||
} else { | ||
return this._doc; | ||
} | ||
this.sendCmd(['lins', this.docID, this.id, lastID, itemID, escaped]); | ||
return this.clone(); | ||
}; | ||
_proto.redo = function redo() { | ||
if (this._doc && Automerge__default.canRedo(this._doc)) { | ||
var newDoc = Automerge__default.redo(this._doc); | ||
this._doc = newDoc; | ||
this._saveOffline('default', newDoc); | ||
this._peer.notify(newDoc); | ||
return newDoc; | ||
} else { | ||
return this._doc; | ||
} | ||
_proto.toArray = function toArray() { | ||
return this.rt.toArray(); | ||
}; | ||
return DocClient; | ||
return ListClient; | ||
}(); | ||
var PRESENCE_NAMESPACE = '/v1/presence'; | ||
var MapClient = /*#__PURE__*/function () { | ||
function MapClient(checkpoint, roomID, docID, mapID, ws) { | ||
this.roomID = roomID; | ||
this.docID = docID; | ||
this.id = mapID; | ||
this.ws = ws; | ||
this.store = {}; // import | ||
function isParsable(val) { | ||
return typeof val === 'object' && val !== null; | ||
} | ||
for (var k in checkpoint) { | ||
var val = checkpoint[k]; | ||
var rateLimittedEmit = /*#__PURE__*/lodash.throttle(function (socket, event) { | ||
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | ||
args[_key - 2] = arguments[_key]; | ||
if (typeof val === 'string') { | ||
this.store[k] = unescape(val); | ||
} | ||
} | ||
} | ||
return Sockets.emit.apply(Sockets, [socket, event].concat(args)); | ||
}, 40, { | ||
leading: true | ||
}); | ||
var _proto = MapClient.prototype; | ||
var PresenceClient = /*#__PURE__*/function () { | ||
function PresenceClient(parameters) { | ||
this._socketURL = ROOM_SERICE_CLIENT_URL; | ||
this._authorizationUrl = parameters.authUrl; | ||
this._roomReference = parameters.roomReference; | ||
} | ||
_proto.sendCmd = function sendCmd(cmd) { | ||
this.ws.send('doc:cmd', { | ||
room: this.roomID, | ||
args: cmd | ||
}); | ||
}; | ||
var _proto = PresenceClient.prototype; | ||
_proto.clone = function clone() { | ||
return Object.assign(Object.create(Object.getPrototypeOf(this)), this); | ||
}; | ||
_proto.init = function init(_ref) { | ||
var _this = this; | ||
_proto.update = function update(cmd) { | ||
if (cmd.length < 3) { | ||
throw new Error('Unexpected command: ' + cmd); | ||
} | ||
var room = _ref.room, | ||
session = _ref.session; | ||
var keyword = cmd[0]; | ||
var docID = cmd[1]; | ||
var id = cmd[2]; | ||
if (!room || !session) { | ||
console.warn('Room Service is offline.'); | ||
return; | ||
if (docID !== this.docID || id !== this.id) { | ||
throw new Error('Command unexpectedly routed to the wrong client'); | ||
} | ||
this._roomId = room.id; | ||
this._socket = Sockets.newSocket(this._socketURL + PRESENCE_NAMESPACE, { | ||
transports: ['websocket'] | ||
}); | ||
Sockets.on(this._socket, 'reconnect_attempt', function () { | ||
!_this._socket ? invariant(false) : void 0; | ||
_this._socket.io.opts.transports = ['websocket']; | ||
}); // Immediately attempt to authorize via traditional auth | ||
this._authorized = authorizeSocket(this._socket, session.token, room.id); | ||
}; | ||
_proto.setPresence = function setPresence(key, value, options) { | ||
try { | ||
var _temp3 = function _temp3() { | ||
var ttl = (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 2; | ||
if (!value) { | ||
console.error("The function call 'setPresence(\"" + key + "\", value)' passed in an undefined, null, or falsey 'value'."); | ||
return; | ||
switch (keyword) { | ||
case 'mput': | ||
if (cmd.length !== 5) { | ||
console.error('Malformed command ', cmd); | ||
break; | ||
} | ||
if (!isParsable(value)) { | ||
console.error("Expected the function call 'setPresence(\"" + key + "\", value)' to use a stringifiable object for variable 'value', instead got '" + value + "'."); | ||
return; | ||
} | ||
var putKey = cmd[3]; | ||
var putVal = cmd[4]; | ||
this.store[putKey] = putVal; | ||
break; | ||
var packet = { | ||
meta: { | ||
roomId: _this3._roomId, | ||
createdAt: new Date().getTime(), | ||
namespace: key, | ||
ttl: ttl | ||
}, | ||
payload: value | ||
}; | ||
rateLimittedEmit(_this3._socket, 'update_presence', packet); | ||
}; | ||
var _this3 = this; | ||
// Offline do nothing | ||
if (!_this3._socket) { | ||
return Promise.resolve(); | ||
} | ||
!_this3._roomId ? "development" !== "production" ? invariant(false, "setPresence is missing a roomId, this is likely a bug with the client. If you're seeing this, please contact us.") : invariant(false) : void 0; // Ensure we're authorized before doing anything | ||
var _temp4 = function () { | ||
if (_this3._authorized) { | ||
return Promise.resolve(_this3._authorized).then(function () {}); | ||
case 'mdel': | ||
if (cmd.length !== 4) { | ||
console.error('Malformed command ', cmd); | ||
break; | ||
} | ||
}(); | ||
return Promise.resolve(_temp4 && _temp4.then ? _temp4.then(_temp3) : _temp3(_temp4)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var delKey = cmd[3]; | ||
delete this.store[delKey]; | ||
break; | ||
_proto.onSetPresence = function onSetPresence(callback) { | ||
var _this4 = this; | ||
// Offline do nothing | ||
if (!this._socket) { | ||
console.warn('offline'); | ||
return; | ||
default: | ||
throw new Error('Unexpected command keyword: ' + keyword); | ||
} | ||
Sockets.on(this._socket, 'update_presence', function (data) { | ||
try { | ||
var _JSON$parse = JSON.parse(data), | ||
meta = _JSON$parse.meta, | ||
payload = _JSON$parse.payload; | ||
return this.clone(); | ||
}; | ||
if (!_this4._roomId) { | ||
throw new Error("Expected a _roomId to be defined before we invoked the the onSetPresence callback. This is a sign of a broken client, please contact us if you're seeing this."); | ||
} | ||
_proto.get = function get(key) { | ||
return this.store[key]; | ||
}; | ||
if (!meta.connectionId) { | ||
console.error("Unexpectedly got a packet without a connection id. We're skipping this for now, but this could be a sign of a service outage or a broken client."); | ||
} // Don't include self | ||
_proto.set = function set(key, value) { | ||
var escaped = escape(value); // Local | ||
this.store[key] = value; // Remote | ||
if (meta.connectionId === _this4._socket.id) { | ||
return Promise.resolve(); | ||
} // This socket event will fire for ALL rooms that we belong | ||
// to, | ||
this.sendCmd(['mput', this.docID, this.id, key, escaped]); | ||
return this.clone(); | ||
}; | ||
_proto["delete"] = function _delete(key) { | ||
// local | ||
delete this.store[key]; // remote | ||
if (meta.roomId !== _this4._roomId) { | ||
return Promise.resolve(); | ||
} | ||
callback(meta, payload); | ||
return Promise.resolve(); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}); | ||
this.sendCmd(['mdel', this.docID, this.id, key]); | ||
return this.clone(); | ||
}; | ||
return PresenceClient; | ||
return MapClient; | ||
}(); | ||
var authorize = function authorize(authorizationUrl, roomReference, headers) { | ||
var createRoom = function createRoom(conn, docsURL, provisionerURL, room, document) { | ||
try { | ||
// Generates and then records a session token | ||
return Promise.resolve(ky.post(authorizationUrl, { | ||
json: { | ||
room: { | ||
reference: roomReference | ||
} | ||
}, | ||
headers: headers || undefined, | ||
// This only works on sites that have setup DNS, | ||
// or the debugger on roomservice.dev/app, which | ||
// uses this SDK. | ||
credentials: authorizationUrl.includes('https://aws.roomservice.dev') && authorizationUrl.includes('debugger-auth-endpoint') ? 'include' : undefined, | ||
throwHttpErrors: false | ||
})).then(function (result) { | ||
// This is just user error, so it's probably fine to throw here. | ||
!(result.status !== 405) ? "development" !== "production" ? invariant(false, 'Your authorization endpoint does not appear to accept a POST request.') : invariant(false) : void 0; | ||
if (result.status < 200 || result.status >= 400) { | ||
throw new Error("Your Auth endpoint at '" + authorizationUrl + "' is not functioning properly, returned status of " + result.status + "."); | ||
} | ||
return Promise.resolve(result.json()).then(function (res) { | ||
var room = res.room, | ||
session = res.session; | ||
return { | ||
room: room, | ||
session: session | ||
}; | ||
return Promise.resolve(fetchSession(provisionerURL, room, document)).then(function (sess) { | ||
return Promise.resolve(fetchDocument(docsURL, sess.token, sess.docID)).then(function (_ref2) { | ||
var body = _ref2.body; | ||
var roomClient = new RoomClient({ | ||
conn: conn, | ||
actor: sess.guestID, | ||
checkpoint: body, | ||
token: sess.token, | ||
roomID: sess.roomID | ||
}); | ||
return Promise.resolve(roomClient.reconnect()).then(function () { | ||
return roomClient; | ||
}); | ||
}); | ||
@@ -840,114 +686,106 @@ }); | ||
}; | ||
var WEBSOCKET_TIMEOUT = 1000 * 2; | ||
var RoomClient = /*#__PURE__*/function () { | ||
function RoomClient(parameters) { | ||
var _this = this; | ||
function RoomClient(params) { | ||
this.ws = new SuperlumeWebSocket(params.conn); | ||
this.token = params.token; | ||
this.roomID = params.roomID; | ||
this.docID = params.checkpoint.id; | ||
this.actor = params.actor; | ||
this.checkpoint = params.checkpoint; | ||
} | ||
this._init = lodash.throttle(function () { | ||
try { | ||
var _temp3 = function _temp3() { | ||
// We're on the server, so we shouldn't init, because we don't need | ||
// to connect to the clients. | ||
if (typeof window === 'undefined') { | ||
// This would signal that the server side can't access the auth endpoint | ||
if (!room) { | ||
throw new Error("Room Service can't access the auth endpoint on the server. More details: https://err.sh/getroomservice/browser/server-side-no-network"); | ||
} | ||
var _proto = RoomClient.prototype; | ||
return { | ||
doc: undefined | ||
}; | ||
} // Presence client | ||
_proto.once = function once(msg) { | ||
try { | ||
var _this2 = this; | ||
var off; | ||
return Promise.race([new Promise(function (_, reject) { | ||
return setTimeout(function () { | ||
return reject('timeout'); | ||
}, WEBSOCKET_TIMEOUT); | ||
}), new Promise(function (resolve) { | ||
off = _this2.ws.bind(msg, function (body) { | ||
resolve(body); | ||
}); | ||
})]).then(function () { | ||
if (off) _this2.ws.unbind(msg, off); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
/** | ||
* TODO: don't expose this function | ||
*/ | ||
; | ||
_this._presenceClient.init({ | ||
room: room, | ||
session: session | ||
}); // Doc client | ||
_proto.reconnect = function reconnect() { | ||
try { | ||
var _this4 = this; | ||
return Promise.resolve(_this._docClient.init({ | ||
room: room, | ||
session: session | ||
})).then(function (_ref) { | ||
var doc = _ref.doc; | ||
return { | ||
doc: doc | ||
}; | ||
}); | ||
}; | ||
var room; | ||
var session; | ||
var _temp4 = _catch(function () { | ||
return Promise.resolve(authorize(_this._authorizationUrl, _this._roomReference, _this._headers)).then(function (params) { | ||
room = params.room; | ||
session = params.session; | ||
}); | ||
}, function (err) { | ||
console.error("Room Service can't access the auth endpoint. More details: https://err.sh/getroomservice/browser/cant-access-auth-endpoint"); | ||
console.warn(err); | ||
if (!_this4.errorListener) { | ||
_this4.errorListener = _this4.ws.bind('error', function (err) { | ||
console.error('Room Service encountered a server-side error. If you see this, please let us know; this could be a bug.', err); | ||
}); | ||
return Promise.resolve(_temp4 && _temp4.then ? _temp4.then(_temp3) : _temp3(_temp4)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}, 100, { | ||
leading: true | ||
}); | ||
this._docClient = new DocClient(parameters); | ||
this._presenceClient = new PresenceClient(parameters); | ||
this._authorizationUrl = parameters.authUrl; | ||
this._roomReference = parameters.roomReference; | ||
this._headers = parameters.headers; | ||
} // @ts-ignore used for testing locally | ||
var authenticated = _this4.once('guest:authenticated'); | ||
var _proto = RoomClient.prototype; | ||
_this4.ws.send('guest:authenticate', _this4.token); | ||
// Start the client, sync from cache, and connect. | ||
// This function is throttled at 100ms, since it's only | ||
// supposed to be called once, but | ||
_proto.init = function init() { | ||
try { | ||
var _this3 = this; | ||
return Promise.resolve(authenticated).then(function () { | ||
var joined = _this4.once('room:joined'); | ||
return Promise.resolve(_this3._init()); | ||
_this4.ws.send('room:join', _this4.roomID); | ||
return Promise.resolve(joined).then(function () {}); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} // Manually restore from cache | ||
; | ||
}; | ||
_proto.restore = function restore() { | ||
_proto.list = function list(name) { | ||
try { | ||
var _this5 = this; | ||
var _this6 = this; | ||
return Promise.resolve(_this5._docClient.restore()); | ||
// create a list if it doesn't exist | ||
if (!_this6.checkpoint.lists[name]) { | ||
_this6.ws.send('doc:cmd', { | ||
args: ['lcreate', _this6.docID, name], | ||
room: _this6.roomID | ||
}); // Assume success | ||
_this6.checkpoint.lists[name] = { | ||
afters: [], | ||
ids: [], | ||
values: [] | ||
}; | ||
} | ||
var l = new ListClient(_this6.checkpoint, _this6.roomID, _this6.docID, name, _this6.ws, _this6.actor); | ||
return Promise.resolve(l); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} // Connection | ||
; | ||
_proto.onConnect = function onConnect(callback) { | ||
this._docClient.onConnect(callback); | ||
}; | ||
_proto.onDisconnect = function onDisconnect(callback) { | ||
this._docClient.onDisconnect(callback); | ||
}; | ||
_proto.map = function map(name) { | ||
try { | ||
var _this8 = this; | ||
_proto.disconnect = function disconnect() { | ||
this._docClient.disconnect(); | ||
} // Documents | ||
; | ||
// Create this map if it doesn't exist | ||
if (!_this8.checkpoint.maps[name]) { | ||
_this8.ws.send('doc:cmd', { | ||
args: ['mcreate', _this8.docID, name], | ||
room: _this8.roomID | ||
}); | ||
} | ||
_proto.setDoc = function setDoc(change) { | ||
try { | ||
var _this7 = this; | ||
return Promise.resolve(_this7._docClient.setDoc(change)); | ||
var m = new MapClient(_this8.checkpoint.maps[name] || {}, _this8.roomID, _this8.docID, name, _this8.ws); | ||
return Promise.resolve(m); | ||
} catch (e) { | ||
@@ -958,62 +796,56 @@ return Promise.reject(e); | ||
_proto.onSetDoc = function onSetDoc(callback) { | ||
this._docClient.onSetDoc(callback); | ||
}; | ||
_proto.onUpdate = function onUpdate(obj, onChangeFn) { | ||
var _this9 = this; | ||
_proto.undo = function undo() { | ||
return this._docClient.undo(); | ||
}; | ||
var bound = this.ws.bind('doc:fwd', function (body) { | ||
if (body.room !== _this9.roomID) return; | ||
_proto.redo = function redo() { | ||
return this._docClient.redo(); | ||
} // Presence | ||
; | ||
if (!body.args || body.args.length < 3) { | ||
// Potentially a network failure, we don't want to crash, | ||
// but do want to warn people | ||
console.error('Unexpected command: ', body.args); | ||
return; | ||
} // Ignore validated commands | ||
_proto.setPresence = function setPresence(key, value) { | ||
this._presenceClient.setPresence(key, value); | ||
if (body.from === _this9.actor) return; | ||
var _ref = [body.args[1], body.args[2]], | ||
docID = _ref[0], | ||
objID = _ref[1]; | ||
if (docID !== _this9.docID) return; | ||
if (objID !== obj.id) return; | ||
onChangeFn(body.args, body.from); | ||
}); | ||
return bound; | ||
}; | ||
_proto.onSetPresence = function onSetPresence(callback) { | ||
this._presenceClient.onSetPresence(callback); | ||
_proto.off = function off(listener) { | ||
this.ws.unbind('doc:fwd', listener); | ||
}; | ||
_createClass(RoomClient, [{ | ||
key: "_socketURL", | ||
set: function set(url) { | ||
this._docClient._socketURL = url; | ||
this._presenceClient._socketURL = url; | ||
} | ||
}]); | ||
return RoomClient; | ||
}(); | ||
var RoomServiceClient = /*#__PURE__*/function () { | ||
function RoomServiceClient(parameters) { | ||
this._roomPool = {}; | ||
this._authorizationUrl = parameters.authUrl; | ||
this._headers = parameters.headers; | ||
var RoomService = /*#__PURE__*/function () { | ||
function RoomService(params) { | ||
this.authURL = params.authURL; | ||
} | ||
var _proto = RoomServiceClient.prototype; | ||
var _proto = RoomService.prototype; | ||
_proto.room = function room(roomReference, defaultDoc) { | ||
if (this._roomPool[roomReference]) { | ||
return this._roomPool[roomReference]; | ||
_proto.room = function room(name) { | ||
try { | ||
var _this2 = this; | ||
var ws = new WebSocket(WS_URL); | ||
return Promise.resolve(createRoom(ws, DOCS_URL, _this2.authURL, name, 'default')); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
var room = new RoomClient({ | ||
authUrl: this._authorizationUrl, | ||
roomReference: roomReference, | ||
defaultDoc: defaultDoc, | ||
headers: this._headers | ||
}); | ||
this._roomPool[roomReference] = room; | ||
return room; | ||
}; | ||
return RoomServiceClient; | ||
return RoomService; | ||
}(); | ||
exports.default = RoomServiceClient; | ||
exports.RoomService = RoomService; | ||
//# sourceMappingURL=browser.cjs.development.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var t=require("automerge"),o=e(t),n=require("immutable"),r=e(require("invariant")),i=require("lodash"),c=require("manymerge"),s=e(require("safe-json-stringify")),a=require("idb-keyval"),u=e(require("uuid/v4")),f=e(require("socket.io-client")),d=e(require("ky-universal"));function l(e,t){try{var o=e()}catch(e){return t(e)}return o&&o.then?o.then(void 0,t):o}"undefined"!=typeof Symbol&&(Symbol.iterator||(Symbol.iterator=Symbol("Symbol.iterator"))),"undefined"!=typeof Symbol&&(Symbol.asyncIterator||(Symbol.asyncIterator=Symbol("Symbol.asyncIterator")));var h=function(){try{return"undefined"==typeof window&&r(!1),Promise.resolve(l((function(){return Promise.resolve(a.get("rs:actor")).then((function(e){if(e)return e;var t=u();return a.set("rs:actor",t),t}))}),(function(){return console.warn("Cant use offline mode in this environment, skipping."),null})))}catch(e){return Promise.reject(e)}},m={newSocket:function(e,t){return f(e,t)},on:function(e,t,o){e&&t||r(!1),e.on(t,o)},off:function(e,t){e.off(t)},emit:function(e,t){e&&t||r(!1);for(var o=arguments.length,n=new Array(o>2?o-2:0),i=2;i<o;i++)n[i-2]=arguments[i];e.emit.apply(e,[t].concat(n))},disconnect:function(e){e.disconnect()}},_=function(e,t,o){try{return Promise.resolve(new Promise((function(n){e||r(!1);var i=setTimeout((function(){n(!1)}),15e3);m.emit(e,"authenticate",{meta:{roomId:o},payload:t}),m.on(e,"authenticated",(function(){clearTimeout(i),m.off(e,"authenticated"),n(!0)}))})))}catch(e){return Promise.reject(e)}},v=function(){function e(e){var o=this,n=this;this._sendMsgToSocket=function(e){try{return n._socket?Promise.resolve(n._authorized).then((function(t){t?(n._roomId||r(!1),m.emit(n._socket,"sync_room_state",s({meta:{roomId:n._roomId},payload:{msg:e}}))):console.error("Room Service is unable to authorize")})):Promise.resolve()}catch(e){return Promise.reject(e)}},this._roomReference=e.roomReference,this._defaultDoc=e.defaultDoc,this._peer=new c.Peer(this._sendMsgToSocket),this._socketURL="https://aws.roomservice.dev",this._saveOffline=i.debounce((function(e,n){!function(e,t,o){try{var n=l((function(){return Promise.resolve(a.set("rs:"+e+"/"+t,o)).then((function(){}))}),(function(e){console.warn("Something went wrong saving Room Service's state offline",e)}));Promise.resolve(n&&n.then?n.then((function(){})):void 0)}catch(e){return Promise.reject(e)}}(o._roomReference,e,t.save(n))}),120)}var u=e.prototype;return u.readActorIdThenCreateDoc=function(e){try{var t=this;return Promise.resolve(h()).then((function(o){return t._actorId=o,t.createDoc(o,e)}))}catch(e){return Promise.reject(e)}},u.createDoc=function(e,t){if(this._doc)return this._doc;var n=o.from(t||{},e?{actorId:e}:void 0);return this._doc=n,this._peer.notify(this._doc),this._doc},u.restore=function(){try{var e=function(){return t.syncOfflineCache()},t=this;if("undefined"==typeof window||"undefined"==typeof indexedDB)return Promise.resolve({});var o=function(){if(!t._doc)return Promise.resolve(t.readActorIdThenCreateDoc(t._defaultDoc)).then((function(){}))}();return Promise.resolve(o&&o.then?o.then(e):e())}catch(e){return Promise.reject(e)}},u.init=function(e){var n=e.room,i=e.session;try{var c=function(){var e=!1;function c(c){return e?c:(s._roomId=n.id,s._socket=m.newSocket(s._socketURL+"/v1/doc",{transports:["websocket"]}),m.on(s._socket,"reconnect_attempt",(function(){s._socket||r(!1),s._socket.io.opts.transports=["websocket"]})),m.on(s._socket,"error",(function(e){try{var t=JSON.parse(e);console.error("Error from Socket: "+t.message)}catch(t){console.error("Unparsable error from socket: "+e)}})),s._authorized=_(s._socket,i.token,n.id),m.on(s._socket,"connect",(function(){s._peer.notify(s._doc),s.syncOfflineCache()})),m.on(s._socket,"disconnect",(function(e){"io server disconnect"===e&&console.warn("The RoomService client was forcibly disconnected from the server, likely due to invalid auth.")})),s._onUpdateSocketCallback&&m.on(s._socket,"sync_room_state",s._onUpdateSocketCallback),s._onConnectSocketCallback&&m.on(s._socket,"connect",s._onConnectSocketCallback),s._onDisconnectSocketCallback&&m.on(s._socket,"disconnect",s._onDisconnectSocketCallback),Promise.resolve(fetch(s._socketURL+"/client/v1/rooms/"+n.id+"/documents/default",{headers:{authorization:"Bearer "+i.token}})).then((function(e){if(200!==e.status)throw new Error("Unexpectedly did not find document for room "+n.reference);return Promise.resolve(e.text()).then((function(e){function n(){return{doc:r}}var r,i=l((function(){return r=o.load(e),Promise.resolve(s.syncOfflineCache()).then((function(e){r=t.merge(e,r),s._doc=r,s._peer.notify(s._doc)}))}),(function(e){console.error(e),r={}}));return i&&i.then?i.then(n):n()}))})))}var a=function(){if(!n||!i)return Promise.resolve(s.syncOfflineCache()).then((function(){return e=!0,{doc:s._doc}}))}();return a&&a.then?a.then(c):c(a)},s=this;if("undefined"==typeof window)return Promise.resolve({doc:void 0});var a=function(){if(!s._doc)return Promise.resolve(s.readActorIdThenCreateDoc(s._defaultDoc)).then((function(){}))}();return Promise.resolve(a&&a.then?a.then(c):c())}catch(e){return Promise.reject(e)}},u.disconnect=function(){"undefined"!=typeof window?(this._socket&&m.disconnect(this._socket),this._socket=void 0):console.warn("Attempting to call disconnect on the server, this is a no-op.")},u.onSetDoc=function(e){var t=this;if("undefined"!=typeof window){this._onUpdateSocketCallback&&r(!1);var o=function(o){try{var r=function(){s.msg.clock=n.Map(s.msg.clock);try{var o=t._peer.applyMessage(s.msg,t._doc);if(!o)return;t._doc=o,t._saveOffline("default",t._doc),s.msg.changes&&e(t._doc)}catch(e){if(e.message&&e.message.includes("Inconsistent reuse of sequence number"))return;console.error(e)}},i=JSON.parse(o),c=i.meta,s=i.payload;if(!t._roomId)throw new Error("Expected a _roomId to be defined before we invoked the the onSetDoc callback. This is a sign of a broken client, please contact us if you're seeing this.");if(c.roomId!==t._roomId)return Promise.resolve();if(!s.msg)throw new Error("The room's state object does not include an 'msg' attribute, which could signal a corrupted room. If you're seeing this in production, that's quite bad and represents a fixable bug within the SDK itself. Please let us know and we'll fix it immediately!");var a=function(){if(!t._doc)return Promise.resolve(t.readActorIdThenCreateDoc(t._defaultDoc)).then((function(){}))}();return Promise.resolve(a&&a.then?a.then(r):r())}catch(e){return Promise.reject(e)}};this._socket?m.on(this._socket,"sync_room_state",o):this._onUpdateSocketCallback=o}else console.warn("Attempting to call onSetDoc on the server, this is a no-op.")},u.onConnect=function(e){"undefined"!=typeof window?this._socket?this._socket.on("connect",e):this._onConnectSocketCallback=e:console.warn("Attempting to call onConnect on the server, this is a no-op.")},u.onDisconnect=function(e){"undefined"!=typeof window?this._socket?this._socket.on("disconnect",e):this._onDisconnectSocketCallback=e:console.warn("Attempting to call onDisconnect on the server, this is a no-op.")},u.syncOfflineCache=function(){try{var e=this;return Promise.resolve(function(e,t){try{return Promise.resolve(l((function(){return Promise.resolve(a.get("rs:"+e+"/default"))}),(function(e){return console.warn("Something went wrong getting Room Service's state offline",e),""})))}catch(e){return Promise.reject(e)}}(e._roomReference)).then((function(o){return o?Promise.resolve(h()).then((function(n){n||console.error("Unexpectedly didn't find offline support in an environment like a browser where we should have offline support.");var r=t.load(o,{actorId:n});return e._doc=r,e._peer.notify(e._doc),r})):e._doc}))}catch(e){return Promise.reject(e)}},u.setDoc=function(e){try{var t=function(){if("function"!=typeof e)throw new Error("room.publishDoc expects a function.");var t=o.change(n._doc,e);return t||(n._actorId||r(!1),t=n.createDoc(n._actorId,n._defaultDoc)),n._doc=t,n._saveOffline("default",t),n._peer.notify(t),t},n=this;if("undefined"==typeof window)return console.warn("Attempting to call setDoc on the server, this is a no-op."),Promise.resolve({});var i=function(){if(!n._doc)return Promise.resolve(n.readActorIdThenCreateDoc(n._defaultDoc)).then((function(e){n._doc=e}))}();return Promise.resolve(i&&i.then?i.then(t):t())}catch(e){return Promise.reject(e)}},u.undo=function(){if(this._doc&&o.canUndo(this._doc)){var e=o.undo(this._doc);return this._doc=e,this._saveOffline("default",e),this._peer.notify(e),e}return this._doc},u.redo=function(){if(this._doc&&o.canRedo(this._doc)){var e=o.redo(this._doc);return this._doc=e,this._saveOffline("default",e),this._peer.notify(e),e}return this._doc},e}(),p=i.throttle((function(e,t){for(var o=arguments.length,n=new Array(o>2?o-2:0),r=2;r<o;r++)n[r-2]=arguments[r];return m.emit.apply(m,[e,t].concat(n))}),40,{leading:!0}),y=function(){function e(e){this._socketURL="https://aws.roomservice.dev",this._authorizationUrl=e.authUrl,this._roomReference=e.roomReference}var t=e.prototype;return t.init=function(e){var t=this,o=e.room,n=e.session;o&&n?(this._roomId=o.id,this._socket=m.newSocket(this._socketURL+"/v1/presence",{transports:["websocket"]}),m.on(this._socket,"reconnect_attempt",(function(){t._socket||r(!1),t._socket.io.opts.transports=["websocket"]})),this._authorized=_(this._socket,n.token,o.id)):console.warn("Room Service is offline.")},t.setPresence=function(e,t,o){try{var n=function(){var n=(null==o?void 0:o.ttl)||2e3;if(t)if("object"==typeof(r=t)&&null!==r){var r,c={meta:{roomId:i._roomId,createdAt:(new Date).getTime(),namespace:e,ttl:n},payload:t};p(i._socket,"update_presence",c)}else console.error("Expected the function call 'setPresence(\""+e+"\", value)' to use a stringifiable object for variable 'value', instead got '"+t+"'.");else console.error("The function call 'setPresence(\""+e+"\", value)' passed in an undefined, null, or falsey 'value'.")},i=this;if(!i._socket)return Promise.resolve();i._roomId||r(!1);var c=function(){if(i._authorized)return Promise.resolve(i._authorized).then((function(){}))}();return Promise.resolve(c&&c.then?c.then(n):n())}catch(e){return Promise.reject(e)}},t.onSetPresence=function(e){var t=this;this._socket?m.on(this._socket,"update_presence",(function(o){try{var n=JSON.parse(o),r=n.meta,i=n.payload;if(!t._roomId)throw new Error("Expected a _roomId to be defined before we invoked the the onSetPresence callback. This is a sign of a broken client, please contact us if you're seeing this.");return r.connectionId||console.error("Unexpectedly got a packet without a connection id. We're skipping this for now, but this could be a sign of a service outage or a broken client."),r.connectionId===t._socket.id||r.roomId!==t._roomId||e(r,i),Promise.resolve()}catch(e){return Promise.reject(e)}})):console.warn("offline")},e}(),k=function(){function e(e){var t=this;this._init=i.throttle((function(){try{var e,o,n=function(){if("undefined"==typeof window){if(!e)throw new Error("Room Service can't access the auth endpoint on the server. More details: https://err.sh/getroomservice/browser/server-side-no-network");return{doc:void 0}}return t._presenceClient.init({room:e,session:o}),Promise.resolve(t._docClient.init({room:e,session:o})).then((function(e){return{doc:e.doc}}))},i=l((function(){return Promise.resolve(function(e,t,o){try{return Promise.resolve(d.post(e,{json:{room:{reference:t}},headers:o||void 0,credentials:e.includes("https://aws.roomservice.dev")&&e.includes("debugger-auth-endpoint")?"include":void 0,throwHttpErrors:!1})).then((function(t){if(405===t.status&&r(!1),t.status<200||t.status>=400)throw new Error("Your Auth endpoint at '"+e+"' is not functioning properly, returned status of "+t.status+".");return Promise.resolve(t.json()).then((function(e){return{room:e.room,session:e.session}}))}))}catch(e){return Promise.reject(e)}}(t._authorizationUrl,t._roomReference,t._headers)).then((function(t){e=t.room,o=t.session}))}),(function(e){console.error("Room Service can't access the auth endpoint. More details: https://err.sh/getroomservice/browser/cant-access-auth-endpoint"),console.warn(e)}));return Promise.resolve(i&&i.then?i.then(n):n())}catch(e){return Promise.reject(e)}}),100,{leading:!0}),this._docClient=new v(e),this._presenceClient=new y(e),this._authorizationUrl=e.authUrl,this._roomReference=e.roomReference,this._headers=e.headers}var t,o=e.prototype;return o.init=function(){try{return Promise.resolve(this._init())}catch(e){return Promise.reject(e)}},o.restore=function(){try{return Promise.resolve(this._docClient.restore())}catch(e){return Promise.reject(e)}},o.onConnect=function(e){this._docClient.onConnect(e)},o.onDisconnect=function(e){this._docClient.onDisconnect(e)},o.disconnect=function(){this._docClient.disconnect()},o.setDoc=function(e){try{return Promise.resolve(this._docClient.setDoc(e))}catch(e){return Promise.reject(e)}},o.onSetDoc=function(e){this._docClient.onSetDoc(e)},o.undo=function(){return this._docClient.undo()},o.redo=function(){return this._docClient.redo()},o.setPresence=function(e,t){this._presenceClient.setPresence(e,t)},o.onSetPresence=function(e){this._presenceClient.onSetPresence(e)},(t=[{key:"_socketURL",set:function(e){this._docClient._socketURL=e,this._presenceClient._socketURL=e}}])&&function(e,t){for(var o=0;o<t.length;o++){var n=t[o];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}(e.prototype,t),e}();exports.default=function(){function e(e){this._roomPool={},this._authorizationUrl=e.authUrl,this._headers=e.headers}return e.prototype.room=function(e,t){if(this._roomPool[e])return this._roomPool[e];var o=new k({authUrl:this._authorizationUrl,roomReference:e,defaultDoc:t,headers:this._headers});return this._roomPool[e]=o,o},e}(); | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t,e=(t=require("tiny-invariant"))&&"object"==typeof t&&"default"in t?t.default:t,r=function(){function t(t){var e=this;this.callbacks={},this.lastTime=0,this.msgsThisMilisecond=0,this.conn=t,this.conn.onmessage=function(t){var r=JSON.parse(t.data);e.dispatch(r.type,r.body)}}var e=t.prototype;return e.timestamp=function(){var t=Date.now();return t===this.lastTime?this.msgsThisMilisecond++:(this.lastTime=t,this.msgsThisMilisecond=0),t+":"+this.msgsThisMilisecond},e.send=function(t,e){var r=this.timestamp();this.conn.send(JSON.stringify({type:t,ts:r,ver:0,body:e}))},e.bind=function(t,e){return this.callbacks[t]=this.callbacks[t]||[],this.callbacks[t].push(e),e},e.unbind=function(t,e){this.callbacks[t]=this.callbacks[t].filter((function(t){return t!==e}))},e.dispatch=function(t,e){var r=this.callbacks[t];if(r)for(var n=0;n<r.length;n++)r[n](e)},t}();function n(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r<e;r++)n[r]=t[r];return n}function o(t,e){var r;if("undefined"==typeof Symbol||null==t[Symbol.iterator]){if(Array.isArray(t)||(r=function(t,e){if(t){if("string"==typeof t)return n(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?n(t,void 0):void 0}}(t))||e&&t&&"number"==typeof t.length){r&&(t=r);var o=0;return function(){return o>=t.length?{done:!0}:{done:!1,value:t[o++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(r=t[Symbol.iterator]()).next.bind(r)}function i(t,e){if("root"===e)return"root";var r=e.split(":");return r[0]+":"+t.actors[parseInt(r[1])]}var s=function(){function t(t){this.count=0,this.actor=t,this.nodes={},this.log=[]}var r,n=t.prototype;return n.import=function(t,r){t||e(!1);for(var n=t.lists[r],o=n.afters||[],s=n.ids||[],c=n.values||[],h=0;h<o.length;h++){var u={after:i(t,o[h]),id:i(t,s[h]),value:c[h]};this.nodes[u.id]=u,this.log.push(u)}this.count=this.log.length},n.get=function(t){if(this.nodes[t])return this.nodes[t].value},n.insert=function(t,r,n){this.log||e(!1);var o=n;o||(o=this.count+":"+this.actor,this.count++);var i={after:t,value:r,id:o};return this.nodes[o]=i,this.log.push(i),o},n.put=function(t,e){this.nodes[t]&&(this.nodes[t].value=e)},n.has=function(t){return!!this.nodes[t]},n.delete=function(t){this.nodes[t].value={t:""}},n.toTree=function(){for(var t,e={children:[],id:"root",value:""},r={root:e},n=o(this.log);!(t=n()).done;){var i=t.value,s={children:[],id:i.id,value:i.value};if(r[i.id]=s,"root"===i.after)e.children.push(s);else{if(!r[i.after])throw new Error("Unexpectedly missing node "+i.after);r[i.after].children.push(s)}}return e},n.sortLog=function(){this.log.sort((function(t,e){var r=t.id.split(":"),n=r[0],o=r[1],i=e.id.split(":"),s=i[0];return n===s?o.localeCompare(i[1]):parseInt(n)-parseInt(s)}))},n.lastID=function(){return this.sortLog(),function t(e){return e.children&&0!==e.children.length?t(e.children[0]):e}(this.toTree()).id},n.toArray=function(){return this.sortLog(),function t(e){if(!e.children||0===e.children.length)return[];for(var r,n=[],i=o(e.children);!(r=i()).done;){var s=r.value,c=s.value;if("string"!=typeof s.value){if(""===s.value.t){n=n.concat([].concat(t(s)));continue}throw new Error("Unimplemented")}n=n.concat([c].concat(t(s)))}return n}(this.toTree())},(r=[{key:"length",get:function(){return Object.keys(this.nodes).length}}])&&function(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,r),t}();function c(t){return"number"==typeof t?""+t:'"'+t+'"'}var h=function(){function t(t,e,r,n,o,c){this.itemIDs=[],this.roomID=e,this.docID=r,this.id=n,this.ws=o,this.rt=new s(c),this.rt.import(t,n);for(var h=t.lists[n].ids||[],u=0;u<h.length;u++)this.itemIDs.push(i(t,h[u]))}var e=t.prototype;return e.sendCmd=function(t){this.ws.send("doc:cmd",{room:this.roomID,args:t})},e.clone=function(){return Object.assign(Object.create(Object.getPrototypeOf(this)),this)},e.update=function(t){if(t.length<3)throw new Error("Unexpected command: "+t);var e=t[0];if(t[1]!==this.docID||t[2]!==this.id)throw new Error("Command unexpectedly routed to the wrong client");switch(e){case"lins":this.rt.insert(t[3],t[5],t[4]);break;case"lput":this.rt.put(t[3],t[4]);break;case"ldel":this.rt.delete(t[3]);break;default:throw new Error("Unexpected command keyword: "+e)}return this.clone()},e.get=function(t){var e=this.itemIDs[t];if(e){var r=this.rt.get(e);if(r){if("object"==typeof r){if(""===r.t)return;throw new Error("Unimplemented references")}return(n=r).length>=2&&'"'===n[0]&&'"'===n[n.length-1]?n:parseInt(n);var n}}},e.set=function(t,e){var r=this.itemIDs[t];if(!r)throw new Error("Unexpected");var n=c(e);return this.rt.put(r,n),this.sendCmd(["lput",this.docID,this.id,r,n]),this.clone()},e.delete=function(t){var e=this.itemIDs[t];return e?(this.rt.delete(e),this.sendCmd(["ldel",this.docID,this.id,e]),Object.assign(Object.create(Object.getPrototypeOf(this)),this),this.clone()):Object.assign({},this)},e.insertAfter=function(t,e){var r=this.itemIDs[t];if(!r)throw new RangeError("List '"+this.id+"' has no index: '"+t+"'");var n=c(e),o=this.rt.insert(r,n);return this.itemIDs.splice(t,0,o),this.sendCmd(["lins",this.docID,this.id,r,o,n]),this.clone()},e.push=function(t){var e="root";0!==this.itemIDs.length&&(e=this.itemIDs[this.itemIDs.length-1]);var r=c(t),n=this.rt.insert(e,r);return this.itemIDs.push(n),this.sendCmd(["lins",this.docID,this.id,e,n,r]),this.clone()},e.toArray=function(){return this.rt.toArray()},t}(),u=function(){function t(t,e,r,n,o){for(var i in this.roomID=e,this.docID=r,this.id=n,this.ws=o,this.store={},t){var s=t[i];"string"==typeof s&&(this.store[i]=unescape(s))}}var e=t.prototype;return e.sendCmd=function(t){this.ws.send("doc:cmd",{room:this.roomID,args:t})},e.clone=function(){return Object.assign(Object.create(Object.getPrototypeOf(this)),this)},e.update=function(t){if(t.length<3)throw new Error("Unexpected command: "+t);var e=t[0];if(t[1]!==this.docID||t[2]!==this.id)throw new Error("Command unexpectedly routed to the wrong client");switch(e){case"mput":if(5!==t.length){console.error("Malformed command ",t);break}this.store[t[3]]=t[4];break;case"mdel":if(4!==t.length){console.error("Malformed command ",t);break}delete this.store[t[3]];break;default:throw new Error("Unexpected command keyword: "+e)}return this.clone()},e.get=function(t){return this.store[t]},e.set=function(t,e){var r=c(e);return this.store[t]=e,this.sendCmd(["mput",this.docID,this.id,t,r]),this.clone()},e.delete=function(t){return delete this.store[t],this.sendCmd(["mdel",this.docID,this.id,t]),this.clone()},t}(),a=function(){function t(t){this.ws=new r(t.conn),this.token=t.token,this.roomID=t.roomID,this.docID=t.checkpoint.id,this.actor=t.actor,this.checkpoint=t.checkpoint}var e=t.prototype;return e.once=function(t){try{var e,r=this;return Promise.race([new Promise((function(t,e){return setTimeout((function(){return e("timeout")}),2e3)})),new Promise((function(n){e=r.ws.bind(t,(function(t){n(t)}))}))]).then((function(){e&&r.ws.unbind(t,e)}))}catch(t){return Promise.reject(t)}},e.reconnect=function(){try{var t=this;t.errorListener||(t.errorListener=t.ws.bind("error",(function(t){console.error("Room Service encountered a server-side error. If you see this, please let us know; this could be a bug.",t)})));var e=t.once("guest:authenticated");return t.ws.send("guest:authenticate",t.token),Promise.resolve(e).then((function(){var e=t.once("room:joined");return t.ws.send("room:join",t.roomID),Promise.resolve(e).then((function(){}))}))}catch(t){return Promise.reject(t)}},e.list=function(t){try{this.checkpoint.lists[t]||(this.ws.send("doc:cmd",{args:["lcreate",this.docID,t],room:this.roomID}),this.checkpoint.lists[t]={afters:[],ids:[],values:[]});var e=new h(this.checkpoint,this.roomID,this.docID,t,this.ws,this.actor);return Promise.resolve(e)}catch(t){return Promise.reject(t)}},e.map=function(t){try{this.checkpoint.maps[t]||this.ws.send("doc:cmd",{args:["mcreate",this.docID,t],room:this.roomID});var e=new u(this.checkpoint.maps[t]||{},this.roomID,this.docID,t,this.ws);return Promise.resolve(e)}catch(t){return Promise.reject(t)}},e.onUpdate=function(t,e){var r=this;return this.ws.bind("doc:fwd",(function(n){if(n.room===r.roomID)if(!n.args||n.args.length<3)console.error("Unexpected command: ",n.args);else if(n.from!==r.actor){var o=[n.args[1],n.args[2]];o[0]===r.docID&&o[1]===t.id&&e(n.args,n.from)}}))},e.off=function(t){this.ws.unbind("doc:fwd",t)},t}();exports.RoomService=function(){function t(t){this.authURL=t.authURL}return t.prototype.room=function(t){try{var e=new WebSocket("wss://super.roomservice.dev/ws");return Promise.resolve(function(t,e,r,n,o){try{return Promise.resolve(function(t,e,r){try{return Promise.resolve(fetch(t,{method:"POST",body:JSON.stringify({resources:[{object:"document",reference:"default",permission:"read_write"},{object:"room",reference:e,permission:"join"}]})})).then((function(e){if(401===e.status)throw new Error("AuthURL returned unauthorized");return Promise.resolve(e.json()).then((function(e){var r=e.token,n=e.guest_id,o=e.resources;if(!o||!r||!n)throw new Error("Invalid response from the AuthURL: "+t);return{token:r,guestID:n,docID:o.find((function(t){return"document"===t.object})).id,roomID:o.find((function(t){return"room"===t.object})).id}}))}))}catch(t){return Promise.reject(t)}}(r,n)).then((function(e){return Promise.resolve(function(t,e,r){try{return Promise.resolve(fetch("https://super.roomservice.dev/"+r,{headers:{Authorization:"Bearer: "+e}})).then((function(t){return Promise.resolve(t.json())}))}catch(t){return Promise.reject(t)}}(0,e.token,e.docID)).then((function(r){var n=new a({conn:t,actor:e.guestID,checkpoint:r.body,token:e.token,roomID:e.roomID});return Promise.resolve(n.reconnect()).then((function(){return n}))}))}))}catch(t){return Promise.reject(t)}}(e,0,this.authURL,t))}catch(t){return Promise.reject(t)}},t}(); | ||
//# sourceMappingURL=browser.cjs.production.min.js.map |
@@ -1,147 +0,118 @@ | ||
import Automerge, { merge, load, save } from 'automerge'; | ||
import { Map } from 'immutable'; | ||
import invariant from 'invariant'; | ||
import { debounce, throttle } from 'lodash-es'; | ||
import { Peer } from 'manymerge'; | ||
import safeJsonStringify from 'safe-json-stringify'; | ||
import { get, set } from 'idb-keyval'; | ||
import uuid from 'uuid/v4'; | ||
import IO from 'socket.io-client'; | ||
import ky from 'ky-universal'; | ||
import invariant from 'tiny-invariant'; | ||
function _defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
// export const WS_URL = 'ws://localhost:3452'; | ||
// export const DOCS_URL = 'http://localhost:3454'; | ||
var WS_URL = 'wss://super.roomservice.dev/ws'; | ||
var DOCS_URL = 'https://super.roomservice.dev'; | ||
function _createClass(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
var SuperlumeWebSocket = /*#__PURE__*/function () { | ||
function SuperlumeWebSocket(conn) { | ||
var _this = this; | ||
// A type of promise-like that resolves synchronously and supports only one observer | ||
this.callbacks = {}; | ||
this.lastTime = 0; | ||
this.msgsThisMilisecond = 0; | ||
this.conn = conn; | ||
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator"; | ||
this.conn.onmessage = function (ev) { | ||
var msg = JSON.parse(ev.data); | ||
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator"; | ||
_this.dispatch(msg.type, msg.body); | ||
}; | ||
} | ||
// Asynchronously call a function and send errors to recovery continuation | ||
function _catch(body, recover) { | ||
try { | ||
var result = body(); | ||
} catch(e) { | ||
return recover(e); | ||
} | ||
if (result && result.then) { | ||
return result.then(void 0, recover); | ||
} | ||
return result; | ||
} | ||
var _proto = SuperlumeWebSocket.prototype; | ||
var ROOM_SERICE_CLIENT_URL = 'https://aws.roomservice.dev'; | ||
_proto.timestamp = function timestamp() { | ||
var time = Date.now(); | ||
var Offline = { | ||
getDoc: function (roomRef, docId) { | ||
try { | ||
return Promise.resolve(_catch(function () { | ||
return Promise.resolve(get('rs:' + roomRef + '/' + docId)); | ||
}, function (err) { | ||
console.warn("Something went wrong getting Room Service's state offline", err); | ||
return ''; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
if (time === this.lastTime) { | ||
this.msgsThisMilisecond++; | ||
} else { | ||
this.lastTime = time; | ||
this.msgsThisMilisecond = 0; | ||
} | ||
}, | ||
setDoc: function (roomRef, docId, value) { | ||
try { | ||
var _temp2 = _catch(function () { | ||
return Promise.resolve(set('rs:' + roomRef + '/' + docId, value)).then(function () {}); | ||
}, function (err) { | ||
console.warn("Something went wrong saving Room Service's state offline", err); | ||
}); | ||
return Promise.resolve(_temp2 && _temp2.then ? _temp2.then(function () {}) : void 0); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}, | ||
getOrCreateActor: function () { | ||
try { | ||
!(typeof window !== 'undefined') ? process.env.NODE_ENV !== "production" ? invariant(false, "getOrCreateActor was used on the server side; this is a bug in the client, if you're seeing this, let us know.") : invariant(false) : void 0; | ||
return Promise.resolve(_catch(function () { | ||
return Promise.resolve(get('rs:actor')).then(function (actor) { | ||
if (actor) { | ||
return actor; | ||
} | ||
return time + ":" + this.msgsThisMilisecond; | ||
}; | ||
var id = uuid(); | ||
set('rs:actor', id); | ||
return id; | ||
}); | ||
}, function () { | ||
console.warn('Cant use offline mode in this environment, skipping.'); | ||
return null; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
}; | ||
_proto.send = function send(msgType, body) { | ||
var ts = this.timestamp(); | ||
var msg = { | ||
type: msgType, | ||
ts: ts, | ||
ver: 0, | ||
body: body | ||
}; | ||
this.conn.send(JSON.stringify(msg)); | ||
}; | ||
/** | ||
* This is just a wrapper around Socket.io that's easier | ||
* to test. | ||
*/ | ||
_proto.bind = function bind(msgType, callback) { | ||
this.callbacks[msgType] = this.callbacks[msgType] || []; | ||
this.callbacks[msgType].push(callback); | ||
return callback; | ||
}; | ||
var Sockets = { | ||
newSocket: function newSocket(url, opts) { | ||
return IO(url, opts); | ||
}, | ||
on: function on(socket, event, fn) { | ||
!(!!socket && !!event) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Requires socket defined') : invariant(false) : void 0; | ||
socket.on(event, fn); | ||
}, | ||
off: function off(socket, event) { | ||
socket.off(event); | ||
}, | ||
emit: function emit(socket, event) { | ||
!(!!socket && !!event) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Requires socket defined') : invariant(false) : void 0; | ||
_proto.unbind = function unbind(msgType, callback) { | ||
this.callbacks[msgType] = this.callbacks[msgType].filter(function (c) { | ||
return c !== callback; | ||
}); | ||
}; | ||
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | ||
args[_key - 2] = arguments[_key]; | ||
_proto.dispatch = function dispatch(msgType, body) { | ||
var stack = this.callbacks[msgType]; | ||
if (!stack) return; | ||
for (var i = 0; i < stack.length; i++) { | ||
stack[i](body); | ||
} | ||
}; | ||
socket.emit.apply(socket, [event].concat(args)); | ||
}, | ||
disconnect: function disconnect(socket) { | ||
socket.disconnect(); | ||
} | ||
}; | ||
return SuperlumeWebSocket; | ||
}(); | ||
var authorizeSocket = function authorizeSocket(socket, token, roomId) { | ||
var fetchSession = function fetchSession(url, room, document) { | ||
try { | ||
return Promise.resolve(new Promise(function (resolve) { | ||
!socket ? process.env.NODE_ENV !== "production" ? invariant(false, 'Requires socket to be defined') : invariant(false) : void 0; | ||
var timeout = setTimeout(function () { | ||
resolve(false); | ||
}, 15000); | ||
Sockets.emit(socket, 'authenticate', { | ||
meta: { | ||
roomId: roomId | ||
}, | ||
payload: token | ||
return Promise.resolve(fetch(url, { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
resources: [{ | ||
object: 'document', | ||
reference: document, | ||
permission: 'read_write' | ||
}, { | ||
object: 'room', | ||
reference: room, | ||
permission: 'join' | ||
}] | ||
}) | ||
})).then(function (res) { | ||
if (res.status === 401) { | ||
// Todo, make a better path for handling this | ||
throw new Error('AuthURL returned unauthorized'); | ||
} | ||
return Promise.resolve(res.json()).then(function (_ref) { | ||
var token = _ref.token, | ||
guestID = _ref.guest_id, | ||
resources = _ref.resources; | ||
if (!resources || !token || !guestID) { | ||
throw new Error('Invalid response from the AuthURL: ' + url); | ||
} | ||
var docID = resources.find(function (r) { | ||
return r.object === 'document'; | ||
}).id; | ||
var roomID = resources.find(function (r) { | ||
return r.object === 'room'; | ||
}).id; | ||
return { | ||
token: token, | ||
guestID: guestID, | ||
docID: docID, | ||
roomID: roomID | ||
}; | ||
}); | ||
Sockets.on(socket, 'authenticated', function () { | ||
clearTimeout(timeout); | ||
Sockets.off(socket, 'authenticated'); | ||
resolve(true); | ||
}); | ||
})); | ||
}); | ||
} catch (e) { | ||
@@ -151,676 +122,552 @@ return Promise.reject(e); | ||
}; | ||
var fetchDocument = function fetchDocument(url, token, docID) { | ||
try { | ||
return Promise.resolve(fetch(url + '/' + docID, { | ||
headers: { | ||
Authorization: 'Bearer: ' + token | ||
} | ||
})).then(function (res) { | ||
return Promise.resolve(res.json()); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var DOC_NAMESPACE = '/v1/doc'; | ||
function _defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
function asRoomStr(room) { | ||
return safeJsonStringify(room); | ||
function _createClass(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
var DocClient = /*#__PURE__*/function () { | ||
function DocClient(parameters) { | ||
var _this2 = this; | ||
function _unsupportedIterableToArray(o, minLen) { | ||
if (!o) return; | ||
if (typeof o === "string") return _arrayLikeToArray(o, minLen); | ||
var n = Object.prototype.toString.call(o).slice(8, -1); | ||
if (n === "Object" && o.constructor) n = o.constructor.name; | ||
if (n === "Map" || n === "Set") return Array.from(o); | ||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); | ||
} | ||
var _this = this; | ||
function _arrayLikeToArray(arr, len) { | ||
if (len == null || len > arr.length) len = arr.length; | ||
// The manymerge client will call this function when it picks up changes. | ||
// | ||
// WARNING: This function is an arrow function specifically because | ||
// it needs to access this._socket. If you use a regular function, | ||
// it won't work. | ||
this._sendMsgToSocket = function (automergeMsg) { | ||
try { | ||
// we're offline, so don't do anything | ||
if (!_this._socket) { | ||
return Promise.resolve(); | ||
} | ||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; | ||
return Promise.resolve(_this._authorized).then(function (isAuthorized) { | ||
if (!isAuthorized) { | ||
console.error('Room Service is unable to authorize'); | ||
return; | ||
} | ||
return arr2; | ||
} | ||
!_this._roomId ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected a _roomId to exist when publishing. This is a sign of a broken client, if you're seeing this, please contact us.") : invariant(false) : void 0; | ||
var room = { | ||
meta: { | ||
roomId: _this._roomId | ||
}, | ||
payload: { | ||
msg: automergeMsg | ||
} | ||
}; | ||
Sockets.emit(_this._socket, 'sync_room_state', asRoomStr(room)); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
function _createForOfIteratorHelperLoose(o, allowArrayLike) { | ||
var it; | ||
this._roomReference = parameters.roomReference; | ||
this._defaultDoc = parameters.defaultDoc; | ||
this._peer = new Peer(this._sendMsgToSocket); | ||
this._socketURL = ROOM_SERICE_CLIENT_URL; // We define this here so we can debounce the save function | ||
// Otherwise we'll get quite the performance hit | ||
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { | ||
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { | ||
if (it) o = it; | ||
var i = 0; | ||
return function () { | ||
if (i >= o.length) return { | ||
done: true | ||
}; | ||
return { | ||
done: false, | ||
value: o[i++] | ||
}; | ||
}; | ||
} | ||
var saveOffline = function saveOffline(docId, doc) { | ||
Offline.setDoc(_this2._roomReference, docId, save(doc)); | ||
}; | ||
this._saveOffline = debounce(saveOffline, 120); | ||
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); | ||
} | ||
var _proto = DocClient.prototype; | ||
it = o[Symbol.iterator](); | ||
return it.next.bind(it); | ||
} | ||
_proto.readActorIdThenCreateDoc = function readActorIdThenCreateDoc(state) { | ||
try { | ||
var _this4 = this; | ||
function unescapeID(checkpoint, id) { | ||
if (id === 'root') return 'root'; | ||
return Promise.resolve(Offline.getOrCreateActor()).then(function (actorId) { | ||
_this4._actorId = actorId; | ||
return _this4.createDoc(actorId, state); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var _id$split = id.split(':'), | ||
index = _id$split[0], | ||
a = _id$split[1]; | ||
_proto.createDoc = function createDoc(actorId, state) { | ||
if (this._doc) { | ||
return this._doc; | ||
} | ||
return index + ':' + checkpoint.actors[parseInt(a)]; | ||
} | ||
var params = actorId ? { | ||
actorId: actorId | ||
} : undefined; | ||
var defaultDoc = Automerge.from(state || {}, params); // 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. | ||
/** | ||
* A Reverse Tree is one where the children point to the | ||
* parents, instead of the otherway around. | ||
* | ||
* We use a reverse tree because the "insert" operation | ||
* can be done in paralell. | ||
*/ | ||
this._doc = defaultDoc; | ||
var ReverseTree = /*#__PURE__*/function () { | ||
function ReverseTree(actor) { | ||
// The number of operations used by this tree | ||
this.count = 0; | ||
this.actor = actor; | ||
this.nodes = {}; | ||
this.log = []; | ||
} | ||
this._peer.notify(this._doc); | ||
var _proto = ReverseTree.prototype; | ||
return this._doc; | ||
} | ||
/** | ||
* Manually attempt to restore the state from offline storage. | ||
*/ | ||
; | ||
_proto["import"] = function _import(checkpoint, listID) { | ||
!checkpoint ? process.env.NODE_ENV !== "production" ? invariant(false) : invariant(false) : void 0; | ||
var list = checkpoint.lists[listID]; | ||
var afters = list.afters || []; | ||
var ids = list.ids || []; | ||
var values = list.values || []; // Rehydrate the cache | ||
_proto.restore = function restore() { | ||
try { | ||
var _temp3 = function _temp3() { | ||
return _this6.syncOfflineCache(); | ||
for (var i = 0; i < afters.length; i++) { | ||
var node = { | ||
after: unescapeID(checkpoint, afters[i]), | ||
id: unescapeID(checkpoint, ids[i]), | ||
value: values[i] | ||
}; | ||
this.nodes[node.id] = node; | ||
this.log.push(node); | ||
} | ||
var _this6 = this; | ||
this.count = this.log.length; | ||
}; | ||
// We can't restore on the server, or in environments | ||
// where indexedb is not defined | ||
if (typeof window === 'undefined' || typeof indexedDB === 'undefined') { | ||
return Promise.resolve({}); | ||
} | ||
var _temp4 = function () { | ||
if (!_this6._doc) { | ||
return Promise.resolve(_this6.readActorIdThenCreateDoc(_this6._defaultDoc)).then(function () {}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp4 && _temp4.then ? _temp4.then(_temp3) : _temp3(_temp4)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
_proto.get = function get(itemID) { | ||
if (this.nodes[itemID]) { | ||
return this.nodes[itemID].value; | ||
} | ||
} | ||
/** | ||
* Attempts to go online. | ||
*/ | ||
; | ||
_proto.init = function init(_ref) { | ||
var room = _ref.room, | ||
session = _ref.session; | ||
return undefined; | ||
}; | ||
try { | ||
var _temp11 = function _temp11() { | ||
var _exit = false; | ||
_proto.insert = function insert(after, value, externalNewID) { | ||
!this.log ? process.env.NODE_ENV !== "production" ? invariant(false) : invariant(false) : void 0; | ||
var id = externalNewID; | ||
function _temp8(_result) { | ||
if (_exit) return _result; | ||
_this8._roomId = room.id; | ||
_this8._socket = Sockets.newSocket(_this8._socketURL + DOC_NAMESPACE, { | ||
transports: ['websocket'] | ||
}); | ||
Sockets.on(_this8._socket, 'reconnect_attempt', function () { | ||
!_this8._socket ? process.env.NODE_ENV !== "production" ? invariant(false) : invariant(false) : void 0; | ||
_this8._socket.io.opts.transports = ['websocket']; | ||
}); | ||
/** | ||
* Errors | ||
*/ | ||
if (!id) { | ||
id = this.count + ":" + this.actor; | ||
this.count++; | ||
} | ||
Sockets.on(_this8._socket, 'error', function (data) { | ||
try { | ||
var _JSON$parse = JSON.parse(data), | ||
message = _JSON$parse.message; | ||
var node = { | ||
after: after, | ||
value: value, | ||
id: id | ||
}; | ||
this.nodes[id] = node; | ||
this.log.push(node); | ||
return id; | ||
}; | ||
console.error("Error from Socket: " + message); | ||
} catch (err) { | ||
console.error("Unparsable error from socket: " + data); | ||
} | ||
}); // Immediately attempt to authorize via traditional auth | ||
_proto.put = function put(itemID, value) { | ||
if (!!this.nodes[itemID]) { | ||
this.nodes[itemID].value = value; | ||
} | ||
}; | ||
_this8._authorized = authorizeSocket(_this8._socket, session.token, room.id); // Required connect handler | ||
_proto.has = function has(itemID) { | ||
return !!this.nodes[itemID]; | ||
}; | ||
Sockets.on(_this8._socket, 'connect', function () { | ||
_this8._peer.notify(_this8._doc); | ||
_proto["delete"] = function _delete(itemID) { | ||
this.nodes[itemID].value = { | ||
t: '' | ||
}; | ||
}; | ||
_this8.syncOfflineCache(); | ||
}); // Required disconnect handler | ||
_proto.toTree = function toTree() { | ||
var root = { | ||
children: [], | ||
id: 'root', | ||
value: '' | ||
}; | ||
var trees = { | ||
root: root | ||
}; | ||
Sockets.on(_this8._socket, 'disconnect', function (reason) { | ||
if (reason === 'io server disconnect') { | ||
console.warn('The RoomService client was forcibly disconnected from the server, likely due to invalid auth.'); | ||
} | ||
}); | ||
/** | ||
* We don't require these to be defined before hand since they're | ||
* optional | ||
*/ | ||
for (var _iterator = _createForOfIteratorHelperLoose(this.log), _step; !(_step = _iterator()).done;) { | ||
var node = _step.value; | ||
var tree = { | ||
children: [], | ||
id: node.id, | ||
value: node.value | ||
}; | ||
trees[node.id] = tree; | ||
if (_this8._onUpdateSocketCallback) { | ||
Sockets.on(_this8._socket, 'sync_room_state', _this8._onUpdateSocketCallback); | ||
} | ||
if (node.after === 'root') { | ||
root.children.push(tree); | ||
} else { | ||
if (!trees[node.after]) { | ||
throw new Error("Unexpectedly missing node " + node.after); | ||
} | ||
if (_this8._onConnectSocketCallback) { | ||
Sockets.on(_this8._socket, 'connect', _this8._onConnectSocketCallback); | ||
} | ||
trees[node.after].children.push(tree); | ||
} | ||
} | ||
if (_this8._onDisconnectSocketCallback) { | ||
Sockets.on(_this8._socket, 'disconnect', _this8._onDisconnectSocketCallback); | ||
} // Load the document of the room. | ||
return root; | ||
}; | ||
_proto.sortLog = function sortLog() { | ||
this.log.sort(function (a, b) { | ||
var _a$id$split = a.id.split(':'), | ||
leftCount = _a$id$split[0], | ||
leftActor = _a$id$split[1]; | ||
return Promise.resolve(fetch(_this8._socketURL + ("/client/v1/rooms/" + room.id + "/documents/default"), { | ||
headers: { | ||
authorization: 'Bearer ' + session.token | ||
} | ||
})).then(function (result) { | ||
if (result.status !== 200) { | ||
throw new Error("Unexpectedly did not find document for room " + room.reference); | ||
} | ||
var _b$id$split = b.id.split(':'), | ||
rightCount = _b$id$split[0], | ||
rightActor = _b$id$split[1]; | ||
return Promise.resolve(result.text()).then(function (roomStateStr) { | ||
function _temp6() { | ||
return { | ||
doc: state | ||
}; | ||
} | ||
if (leftCount === rightCount) { | ||
return leftActor.localeCompare(rightActor); | ||
} | ||
// Merge RoomService's online cache with what we have locally | ||
var state; | ||
return parseInt(leftCount) - parseInt(rightCount); | ||
}); | ||
}; | ||
var _temp5 = _catch(function () { | ||
// NOTE: we purposefully don't define an actor id, | ||
// since it's not assumed this state is defined by our actor. | ||
state = Automerge.load(roomStateStr); | ||
return Promise.resolve(_this8.syncOfflineCache()).then(function (local) { | ||
state = merge(local, state); | ||
_this8._doc = state; | ||
_proto.lastID = function lastID() { | ||
this.sortLog(); // -- Convert the log into a regular tree | ||
_this8._peer.notify(_this8._doc); | ||
}); | ||
}, function (err) { | ||
console.error(err); | ||
state = {}; | ||
}); | ||
var root = this.toTree(); // Search the left side of the tree | ||
return _temp5 && _temp5.then ? _temp5.then(_temp6) : _temp6(_temp5); | ||
}); | ||
}); | ||
} | ||
var _temp7 = function () { | ||
if (!room || !session) { | ||
return Promise.resolve(_this8.syncOfflineCache()).then(function () { | ||
_exit = true; | ||
return { | ||
doc: _this8._doc | ||
}; | ||
}); | ||
} | ||
}(); | ||
// we're offline, so we should just continue with our fun little world | ||
return _temp7 && _temp7.then ? _temp7.then(_temp8) : _temp8(_temp7); | ||
}; | ||
var _this8 = this; | ||
if (typeof window === 'undefined') { | ||
return Promise.resolve({ | ||
doc: undefined | ||
}); | ||
function left(t) { | ||
if (!t.children || t.children.length === 0) { | ||
return t; | ||
} | ||
var _temp12 = function () { | ||
if (!_this8._doc) { | ||
return Promise.resolve(_this8.readActorIdThenCreateDoc(_this8._defaultDoc)).then(function () {}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp12 && _temp12.then ? _temp12.then(_temp11) : _temp11(_temp12)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
return left(t.children[0]); | ||
} | ||
} | ||
/** | ||
* Manually go offline | ||
*/ | ||
; | ||
_proto.disconnect = function disconnect() { | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call disconnect on the server, this is a no-op.'); | ||
return; | ||
} | ||
if (this._socket) { | ||
Sockets.disconnect(this._socket); | ||
} | ||
this._socket = undefined; | ||
return left(root).id; | ||
}; | ||
_proto.onSetDoc = function onSetDoc(callback) { | ||
var _this9 = this; | ||
_proto.toArray = function toArray() { | ||
this.sortLog(); // -- Convert the log into a regular tree | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call onSetDoc on the server, this is a no-op.'); | ||
return; | ||
} | ||
var root = this.toTree(); // -- Do a depth-first traversal to get the result | ||
!!this._onUpdateSocketCallback ? process.env.NODE_ENV !== "production" ? invariant(false, "It looks like you've called onSetDoc 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.") : invariant(false) : void 0; | ||
function postorder(t) { | ||
if (!t.children || t.children.length === 0) { | ||
return []; | ||
} | ||
var socketCallback = function socketCallback(data) { | ||
try { | ||
var _temp15 = function _temp15() { | ||
// convert the payload clock to a map | ||
payload.msg.clock = Map(payload.msg.clock); | ||
var vals = []; | ||
try { | ||
var newDoc = _this9._peer.applyMessage(payload.msg, _this9._doc); // if we don't have any new changes, we don't need to do anything. | ||
for (var _iterator2 = _createForOfIteratorHelperLoose(t.children), _step2; !(_step2 = _iterator2()).done;) { | ||
var child = _step2.value; | ||
var value = child.value; | ||
if (typeof child.value !== 'string') { | ||
// Skip tombstones | ||
if (child.value.t === '') { | ||
vals = vals.concat([].concat(postorder(child))); | ||
continue; | ||
} | ||
if (!newDoc) { | ||
return; | ||
} | ||
throw new Error('Unimplemented'); | ||
} | ||
_this9._doc = newDoc; | ||
vals = vals.concat([value].concat(postorder(child))); | ||
} | ||
_this9._saveOffline('default', _this9._doc); // From a user's perspective, the document should only update | ||
// if we've actually made changes (since only we care about the | ||
// clock position of everyone else). | ||
return vals; | ||
} | ||
return postorder(root); | ||
}; | ||
if (payload.msg.changes) { | ||
callback(_this9._doc); | ||
} | ||
} catch (err) { | ||
// Ignore Automerge double-apply errors | ||
if (err.message && err.message.includes('Inconsistent reuse of sequence number')) { | ||
return; | ||
} | ||
_createClass(ReverseTree, [{ | ||
key: "length", | ||
get: function get() { | ||
return Object.keys(this.nodes).length; | ||
} | ||
}]); | ||
console.error(err); | ||
} | ||
}; | ||
return ReverseTree; | ||
}(); | ||
var _JSON$parse2 = JSON.parse(data), | ||
meta = _JSON$parse2.meta, | ||
payload = _JSON$parse2.payload; | ||
function escape(value) { | ||
if (typeof value === 'number') return "" + value;else return "\"" + value + "\""; | ||
} | ||
function unescape$1(value) { | ||
if (value.length >= 2 && value[0] === '"' && value[value.length - 1] === '"') { | ||
return value; | ||
} | ||
if (!_this9._roomId) { | ||
throw new Error("Expected a _roomId to be defined before we invoked the the onSetDoc callback. This is a sign of a broken client, please contact us if you're seeing this."); | ||
} // This socket event will fire for ALL rooms, so we need to check | ||
// if this callback refers to this particular room. | ||
return parseInt(value); | ||
} | ||
var ListClient = /*#__PURE__*/function () { | ||
function ListClient(checkpoint, roomID, docID, listID, ws, actor) { | ||
// Map indexes to item ids | ||
this.itemIDs = []; | ||
this.roomID = roomID; | ||
this.docID = docID; | ||
this.id = listID; | ||
this.ws = ws; | ||
this.rt = new ReverseTree(actor); | ||
this.rt["import"](checkpoint, listID); | ||
var list = checkpoint.lists[listID]; | ||
var ids = list.ids || []; | ||
if (meta.roomId !== _this9._roomId) { | ||
return Promise.resolve(); | ||
} | ||
for (var i = 0; i < ids.length; i++) { | ||
this.itemIDs.push(unescapeID(checkpoint, ids[i])); | ||
} | ||
} | ||
if (!payload.msg) { | ||
throw new Error("The room's state object does not include an 'msg' attribute, which could signal a corrupted room. If you're seeing this in production, that's quite bad and represents a fixable bug within the SDK itself. Please let us know and we'll fix it immediately!"); | ||
} // This is effectively impossible tbh, but we like to be cautious | ||
var _proto = ListClient.prototype; | ||
_proto.sendCmd = function sendCmd(cmd) { | ||
this.ws.send('doc:cmd', { | ||
room: this.roomID, | ||
args: cmd | ||
}); | ||
}; | ||
var _temp16 = function () { | ||
if (!_this9._doc) { | ||
return Promise.resolve(_this9.readActorIdThenCreateDoc(_this9._defaultDoc)).then(function () {}); | ||
} | ||
}(); | ||
_proto.clone = function clone() { | ||
return Object.assign(Object.create(Object.getPrototypeOf(this)), this); | ||
}; | ||
return Promise.resolve(_temp16 && _temp16.then ? _temp16.then(_temp15) : _temp15(_temp16)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; // If we're offline, just wait till we're back online to assign this callback | ||
_proto.update = function update(cmd) { | ||
if (cmd.length < 3) { | ||
throw new Error('Unexpected command: ' + cmd); | ||
} | ||
var keyword = cmd[0]; | ||
var docID = cmd[1]; | ||
var id = cmd[2]; | ||
if (!this._socket) { | ||
this._onUpdateSocketCallback = socketCallback; | ||
return; | ||
if (docID !== this.docID || id !== this.id) { | ||
throw new Error('Command unexpectedly routed to the wrong client'); | ||
} | ||
Sockets.on(this._socket, 'sync_room_state', socketCallback); | ||
}; | ||
switch (keyword) { | ||
case 'lins': | ||
var insAfter = cmd[3]; | ||
var insItemID = cmd[4]; | ||
var insValue = cmd[5]; | ||
this.rt.insert(insAfter, insValue, insItemID); | ||
break; | ||
_proto.onConnect = function onConnect(callback) { | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call onConnect on the server, this is a no-op.'); | ||
return; | ||
} // If we're offline, cue this up for later. | ||
case 'lput': | ||
var putItemID = cmd[3]; | ||
var putVal = cmd[4]; | ||
this.rt.put(putItemID, putVal); | ||
break; | ||
case 'ldel': | ||
var delItemID = cmd[3]; | ||
this.rt["delete"](delItemID); | ||
break; | ||
if (!this._socket) { | ||
this._onConnectSocketCallback = callback; | ||
return; | ||
default: | ||
throw new Error('Unexpected command keyword: ' + keyword); | ||
} | ||
this._socket.on('connect', callback); | ||
return this.clone(); | ||
}; | ||
_proto.onDisconnect = function onDisconnect(callback) { | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call onDisconnect on the server, this is a no-op.'); | ||
return; | ||
} // If we're offline, cue this up for later. | ||
_proto.get = function get(index) { | ||
var itemID = this.itemIDs[index]; | ||
if (!itemID) return undefined; | ||
var val = this.rt.get(itemID); | ||
if (!val) return undefined; | ||
if (typeof val === 'object') { | ||
if (val.t === '') { | ||
return undefined; | ||
} | ||
if (!this._socket) { | ||
this._onDisconnectSocketCallback = callback; | ||
return; | ||
throw new Error('Unimplemented references'); | ||
} | ||
this._socket.on('disconnect', callback); | ||
return unescape$1(val); | ||
}; | ||
_proto.syncOfflineCache = function syncOfflineCache() { | ||
try { | ||
var _this11 = this; | ||
_proto.set = function set(index, val) { | ||
var itemID = this.itemIDs[index]; | ||
return Promise.resolve(Offline.getDoc(_this11._roomReference, 'default')).then(function (data) { | ||
return data ? Promise.resolve(Offline.getOrCreateActor()).then(function (actorId) { | ||
if (!actorId) { | ||
console.error("Unexpectedly didn't find offline support in an environment like a browser where we should have offline support."); | ||
} // We explictly do not add | ||
if (!itemID) { | ||
throw new Error('Unexpected'); | ||
} | ||
var escaped = escape(val); // Local | ||
var offlineDoc = load(data, { | ||
actorId: actorId | ||
}); | ||
_this11._doc = offlineDoc; | ||
this.rt.put(itemID, escaped); // Remote | ||
_this11._peer.notify(_this11._doc); | ||
return offlineDoc; | ||
}) : _this11._doc; | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
this.sendCmd(['lput', this.docID, this.id, itemID, escaped]); | ||
return this.clone(); | ||
}; | ||
_proto.setDoc = function setDoc(callback) { | ||
try { | ||
var _temp19 = function _temp19() { | ||
if (typeof callback !== 'function') { | ||
throw new Error("room.publishDoc expects a function."); | ||
} | ||
_proto["delete"] = function _delete(index) { | ||
var itemID = this.itemIDs[index]; | ||
if (!itemID) return Object.assign({}, this); // Local | ||
var newDoc = Automerge.change(_this13._doc, callback); | ||
this.rt["delete"](itemID); // Remote | ||
if (!newDoc) { | ||
!!!_this13._actorId ? process.env.NODE_ENV !== "production" ? invariant(false, "The client is trying to regenerate a deleted document, but isn't able to access the cached actor id. This is probably a bug in the client, if you see this, we're incredibly sorry! Please let us know. In the meantime, you may be able work around this by ensuring 'await room.restore()' has finished before calling 'publishState'.") : invariant(false) : void 0; // this happens if someone deletes the doc, so we should just reinit it. | ||
this.sendCmd(['ldel', this.docID, this.id, itemID]); | ||
Object.assign(Object.create(Object.getPrototypeOf(this)), this); | ||
return this.clone(); | ||
}; | ||
newDoc = _this13.createDoc(_this13._actorId, _this13._defaultDoc); | ||
} | ||
_proto.insertAfter = function insertAfter(index, val) { | ||
var afterID = this.itemIDs[index]; | ||
_this13._doc = newDoc; | ||
if (!afterID) { | ||
throw new RangeError("List '" + this.id + "' has no index: '" + index + "'"); | ||
} | ||
_this13._saveOffline('default', newDoc); | ||
var escaped = escape(val); // Local | ||
_this13._peer.notify(newDoc); | ||
var itemID = this.rt.insert(afterID, escaped); | ||
this.itemIDs.splice(index, 0, itemID); // Remote | ||
return newDoc; | ||
}; | ||
this.sendCmd(['lins', this.docID, this.id, afterID, itemID, escaped]); | ||
return this.clone(); | ||
}; | ||
var _this13 = this; | ||
_proto.push = function push(val) { | ||
var lastID = 'root'; | ||
if (typeof window === 'undefined') { | ||
console.warn('Attempting to call setDoc on the server, this is a no-op.'); | ||
return Promise.resolve({}); | ||
} | ||
var _temp20 = function () { | ||
if (!_this13._doc) { | ||
return Promise.resolve(_this13.readActorIdThenCreateDoc(_this13._defaultDoc)).then(function (_this12$readActorIdTh) { | ||
_this13._doc = _this12$readActorIdTh; | ||
}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp20 && _temp20.then ? _temp20.then(_temp19) : _temp19(_temp20)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
if (this.itemIDs.length !== 0) { | ||
lastID = this.itemIDs[this.itemIDs.length - 1]; | ||
} | ||
}; | ||
_proto.undo = function undo() { | ||
if (this._doc && Automerge.canUndo(this._doc)) { | ||
var newDoc = Automerge.undo(this._doc); | ||
this._doc = newDoc; | ||
var escaped = escape(val); // Local | ||
this._saveOffline('default', newDoc); | ||
var itemID = this.rt.insert(lastID, escaped); | ||
this.itemIDs.push(itemID); // Remote | ||
this._peer.notify(newDoc); | ||
return newDoc; | ||
} else { | ||
return this._doc; | ||
} | ||
this.sendCmd(['lins', this.docID, this.id, lastID, itemID, escaped]); | ||
return this.clone(); | ||
}; | ||
_proto.redo = function redo() { | ||
if (this._doc && Automerge.canRedo(this._doc)) { | ||
var newDoc = Automerge.redo(this._doc); | ||
this._doc = newDoc; | ||
this._saveOffline('default', newDoc); | ||
this._peer.notify(newDoc); | ||
return newDoc; | ||
} else { | ||
return this._doc; | ||
} | ||
_proto.toArray = function toArray() { | ||
return this.rt.toArray(); | ||
}; | ||
return DocClient; | ||
return ListClient; | ||
}(); | ||
var PRESENCE_NAMESPACE = '/v1/presence'; | ||
var MapClient = /*#__PURE__*/function () { | ||
function MapClient(checkpoint, roomID, docID, mapID, ws) { | ||
this.roomID = roomID; | ||
this.docID = docID; | ||
this.id = mapID; | ||
this.ws = ws; | ||
this.store = {}; // import | ||
function isParsable(val) { | ||
return typeof val === 'object' && val !== null; | ||
} | ||
for (var k in checkpoint) { | ||
var val = checkpoint[k]; | ||
var rateLimittedEmit = /*#__PURE__*/throttle(function (socket, event) { | ||
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | ||
args[_key - 2] = arguments[_key]; | ||
if (typeof val === 'string') { | ||
this.store[k] = unescape(val); | ||
} | ||
} | ||
} | ||
return Sockets.emit.apply(Sockets, [socket, event].concat(args)); | ||
}, 40, { | ||
leading: true | ||
}); | ||
var _proto = MapClient.prototype; | ||
var PresenceClient = /*#__PURE__*/function () { | ||
function PresenceClient(parameters) { | ||
this._socketURL = ROOM_SERICE_CLIENT_URL; | ||
this._authorizationUrl = parameters.authUrl; | ||
this._roomReference = parameters.roomReference; | ||
} | ||
_proto.sendCmd = function sendCmd(cmd) { | ||
this.ws.send('doc:cmd', { | ||
room: this.roomID, | ||
args: cmd | ||
}); | ||
}; | ||
var _proto = PresenceClient.prototype; | ||
_proto.clone = function clone() { | ||
return Object.assign(Object.create(Object.getPrototypeOf(this)), this); | ||
}; | ||
_proto.init = function init(_ref) { | ||
var _this = this; | ||
_proto.update = function update(cmd) { | ||
if (cmd.length < 3) { | ||
throw new Error('Unexpected command: ' + cmd); | ||
} | ||
var room = _ref.room, | ||
session = _ref.session; | ||
var keyword = cmd[0]; | ||
var docID = cmd[1]; | ||
var id = cmd[2]; | ||
if (!room || !session) { | ||
console.warn('Room Service is offline.'); | ||
return; | ||
if (docID !== this.docID || id !== this.id) { | ||
throw new Error('Command unexpectedly routed to the wrong client'); | ||
} | ||
this._roomId = room.id; | ||
this._socket = Sockets.newSocket(this._socketURL + PRESENCE_NAMESPACE, { | ||
transports: ['websocket'] | ||
}); | ||
Sockets.on(this._socket, 'reconnect_attempt', function () { | ||
!_this._socket ? process.env.NODE_ENV !== "production" ? invariant(false) : invariant(false) : void 0; | ||
_this._socket.io.opts.transports = ['websocket']; | ||
}); // Immediately attempt to authorize via traditional auth | ||
this._authorized = authorizeSocket(this._socket, session.token, room.id); | ||
}; | ||
_proto.setPresence = function setPresence(key, value, options) { | ||
try { | ||
var _temp3 = function _temp3() { | ||
var ttl = (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 2; | ||
if (!value) { | ||
console.error("The function call 'setPresence(\"" + key + "\", value)' passed in an undefined, null, or falsey 'value'."); | ||
return; | ||
switch (keyword) { | ||
case 'mput': | ||
if (cmd.length !== 5) { | ||
console.error('Malformed command ', cmd); | ||
break; | ||
} | ||
if (!isParsable(value)) { | ||
console.error("Expected the function call 'setPresence(\"" + key + "\", value)' to use a stringifiable object for variable 'value', instead got '" + value + "'."); | ||
return; | ||
} | ||
var putKey = cmd[3]; | ||
var putVal = cmd[4]; | ||
this.store[putKey] = putVal; | ||
break; | ||
var packet = { | ||
meta: { | ||
roomId: _this3._roomId, | ||
createdAt: new Date().getTime(), | ||
namespace: key, | ||
ttl: ttl | ||
}, | ||
payload: value | ||
}; | ||
rateLimittedEmit(_this3._socket, 'update_presence', packet); | ||
}; | ||
var _this3 = this; | ||
// Offline do nothing | ||
if (!_this3._socket) { | ||
return Promise.resolve(); | ||
} | ||
!_this3._roomId ? process.env.NODE_ENV !== "production" ? invariant(false, "setPresence is missing a roomId, this is likely a bug with the client. If you're seeing this, please contact us.") : invariant(false) : void 0; // Ensure we're authorized before doing anything | ||
var _temp4 = function () { | ||
if (_this3._authorized) { | ||
return Promise.resolve(_this3._authorized).then(function () {}); | ||
case 'mdel': | ||
if (cmd.length !== 4) { | ||
console.error('Malformed command ', cmd); | ||
break; | ||
} | ||
}(); | ||
return Promise.resolve(_temp4 && _temp4.then ? _temp4.then(_temp3) : _temp3(_temp4)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var delKey = cmd[3]; | ||
delete this.store[delKey]; | ||
break; | ||
_proto.onSetPresence = function onSetPresence(callback) { | ||
var _this4 = this; | ||
// Offline do nothing | ||
if (!this._socket) { | ||
console.warn('offline'); | ||
return; | ||
default: | ||
throw new Error('Unexpected command keyword: ' + keyword); | ||
} | ||
Sockets.on(this._socket, 'update_presence', function (data) { | ||
try { | ||
var _JSON$parse = JSON.parse(data), | ||
meta = _JSON$parse.meta, | ||
payload = _JSON$parse.payload; | ||
return this.clone(); | ||
}; | ||
if (!_this4._roomId) { | ||
throw new Error("Expected a _roomId to be defined before we invoked the the onSetPresence callback. This is a sign of a broken client, please contact us if you're seeing this."); | ||
} | ||
_proto.get = function get(key) { | ||
return this.store[key]; | ||
}; | ||
if (!meta.connectionId) { | ||
console.error("Unexpectedly got a packet without a connection id. We're skipping this for now, but this could be a sign of a service outage or a broken client."); | ||
} // Don't include self | ||
_proto.set = function set(key, value) { | ||
var escaped = escape(value); // Local | ||
this.store[key] = value; // Remote | ||
if (meta.connectionId === _this4._socket.id) { | ||
return Promise.resolve(); | ||
} // This socket event will fire for ALL rooms that we belong | ||
// to, | ||
this.sendCmd(['mput', this.docID, this.id, key, escaped]); | ||
return this.clone(); | ||
}; | ||
_proto["delete"] = function _delete(key) { | ||
// local | ||
delete this.store[key]; // remote | ||
if (meta.roomId !== _this4._roomId) { | ||
return Promise.resolve(); | ||
} | ||
callback(meta, payload); | ||
return Promise.resolve(); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}); | ||
this.sendCmd(['mdel', this.docID, this.id, key]); | ||
return this.clone(); | ||
}; | ||
return PresenceClient; | ||
return MapClient; | ||
}(); | ||
var authorize = function authorize(authorizationUrl, roomReference, headers) { | ||
var createRoom = function createRoom(conn, docsURL, provisionerURL, room, document) { | ||
try { | ||
// Generates and then records a session token | ||
return Promise.resolve(ky.post(authorizationUrl, { | ||
json: { | ||
room: { | ||
reference: roomReference | ||
} | ||
}, | ||
headers: headers || undefined, | ||
// This only works on sites that have setup DNS, | ||
// or the debugger on roomservice.dev/app, which | ||
// uses this SDK. | ||
credentials: authorizationUrl.includes('https://aws.roomservice.dev') && authorizationUrl.includes('debugger-auth-endpoint') ? 'include' : undefined, | ||
throwHttpErrors: false | ||
})).then(function (result) { | ||
// This is just user error, so it's probably fine to throw here. | ||
!(result.status !== 405) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Your authorization endpoint does not appear to accept a POST request.') : invariant(false) : void 0; | ||
if (result.status < 200 || result.status >= 400) { | ||
throw new Error("Your Auth endpoint at '" + authorizationUrl + "' is not functioning properly, returned status of " + result.status + "."); | ||
} | ||
return Promise.resolve(result.json()).then(function (res) { | ||
var room = res.room, | ||
session = res.session; | ||
return { | ||
room: room, | ||
session: session | ||
}; | ||
return Promise.resolve(fetchSession(provisionerURL, room, document)).then(function (sess) { | ||
return Promise.resolve(fetchDocument(docsURL, sess.token, sess.docID)).then(function (_ref2) { | ||
var body = _ref2.body; | ||
var roomClient = new RoomClient({ | ||
conn: conn, | ||
actor: sess.guestID, | ||
checkpoint: body, | ||
token: sess.token, | ||
roomID: sess.roomID | ||
}); | ||
return Promise.resolve(roomClient.reconnect()).then(function () { | ||
return roomClient; | ||
}); | ||
}); | ||
@@ -832,114 +679,106 @@ }); | ||
}; | ||
var WEBSOCKET_TIMEOUT = 1000 * 2; | ||
var RoomClient = /*#__PURE__*/function () { | ||
function RoomClient(parameters) { | ||
var _this = this; | ||
function RoomClient(params) { | ||
this.ws = new SuperlumeWebSocket(params.conn); | ||
this.token = params.token; | ||
this.roomID = params.roomID; | ||
this.docID = params.checkpoint.id; | ||
this.actor = params.actor; | ||
this.checkpoint = params.checkpoint; | ||
} | ||
this._init = throttle(function () { | ||
try { | ||
var _temp3 = function _temp3() { | ||
// We're on the server, so we shouldn't init, because we don't need | ||
// to connect to the clients. | ||
if (typeof window === 'undefined') { | ||
// This would signal that the server side can't access the auth endpoint | ||
if (!room) { | ||
throw new Error("Room Service can't access the auth endpoint on the server. More details: https://err.sh/getroomservice/browser/server-side-no-network"); | ||
} | ||
var _proto = RoomClient.prototype; | ||
return { | ||
doc: undefined | ||
}; | ||
} // Presence client | ||
_proto.once = function once(msg) { | ||
try { | ||
var _this2 = this; | ||
var off; | ||
return Promise.race([new Promise(function (_, reject) { | ||
return setTimeout(function () { | ||
return reject('timeout'); | ||
}, WEBSOCKET_TIMEOUT); | ||
}), new Promise(function (resolve) { | ||
off = _this2.ws.bind(msg, function (body) { | ||
resolve(body); | ||
}); | ||
})]).then(function () { | ||
if (off) _this2.ws.unbind(msg, off); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
/** | ||
* TODO: don't expose this function | ||
*/ | ||
; | ||
_this._presenceClient.init({ | ||
room: room, | ||
session: session | ||
}); // Doc client | ||
_proto.reconnect = function reconnect() { | ||
try { | ||
var _this4 = this; | ||
return Promise.resolve(_this._docClient.init({ | ||
room: room, | ||
session: session | ||
})).then(function (_ref) { | ||
var doc = _ref.doc; | ||
return { | ||
doc: doc | ||
}; | ||
}); | ||
}; | ||
var room; | ||
var session; | ||
var _temp4 = _catch(function () { | ||
return Promise.resolve(authorize(_this._authorizationUrl, _this._roomReference, _this._headers)).then(function (params) { | ||
room = params.room; | ||
session = params.session; | ||
}); | ||
}, function (err) { | ||
console.error("Room Service can't access the auth endpoint. More details: https://err.sh/getroomservice/browser/cant-access-auth-endpoint"); | ||
console.warn(err); | ||
if (!_this4.errorListener) { | ||
_this4.errorListener = _this4.ws.bind('error', function (err) { | ||
console.error('Room Service encountered a server-side error. If you see this, please let us know; this could be a bug.', err); | ||
}); | ||
return Promise.resolve(_temp4 && _temp4.then ? _temp4.then(_temp3) : _temp3(_temp4)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}, 100, { | ||
leading: true | ||
}); | ||
this._docClient = new DocClient(parameters); | ||
this._presenceClient = new PresenceClient(parameters); | ||
this._authorizationUrl = parameters.authUrl; | ||
this._roomReference = parameters.roomReference; | ||
this._headers = parameters.headers; | ||
} // @ts-ignore used for testing locally | ||
var authenticated = _this4.once('guest:authenticated'); | ||
var _proto = RoomClient.prototype; | ||
_this4.ws.send('guest:authenticate', _this4.token); | ||
// Start the client, sync from cache, and connect. | ||
// This function is throttled at 100ms, since it's only | ||
// supposed to be called once, but | ||
_proto.init = function init() { | ||
try { | ||
var _this3 = this; | ||
return Promise.resolve(authenticated).then(function () { | ||
var joined = _this4.once('room:joined'); | ||
return Promise.resolve(_this3._init()); | ||
_this4.ws.send('room:join', _this4.roomID); | ||
return Promise.resolve(joined).then(function () {}); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} // Manually restore from cache | ||
; | ||
}; | ||
_proto.restore = function restore() { | ||
_proto.list = function list(name) { | ||
try { | ||
var _this5 = this; | ||
var _this6 = this; | ||
return Promise.resolve(_this5._docClient.restore()); | ||
// create a list if it doesn't exist | ||
if (!_this6.checkpoint.lists[name]) { | ||
_this6.ws.send('doc:cmd', { | ||
args: ['lcreate', _this6.docID, name], | ||
room: _this6.roomID | ||
}); // Assume success | ||
_this6.checkpoint.lists[name] = { | ||
afters: [], | ||
ids: [], | ||
values: [] | ||
}; | ||
} | ||
var l = new ListClient(_this6.checkpoint, _this6.roomID, _this6.docID, name, _this6.ws, _this6.actor); | ||
return Promise.resolve(l); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} // Connection | ||
; | ||
_proto.onConnect = function onConnect(callback) { | ||
this._docClient.onConnect(callback); | ||
}; | ||
_proto.onDisconnect = function onDisconnect(callback) { | ||
this._docClient.onDisconnect(callback); | ||
}; | ||
_proto.map = function map(name) { | ||
try { | ||
var _this8 = this; | ||
_proto.disconnect = function disconnect() { | ||
this._docClient.disconnect(); | ||
} // Documents | ||
; | ||
// Create this map if it doesn't exist | ||
if (!_this8.checkpoint.maps[name]) { | ||
_this8.ws.send('doc:cmd', { | ||
args: ['mcreate', _this8.docID, name], | ||
room: _this8.roomID | ||
}); | ||
} | ||
_proto.setDoc = function setDoc(change) { | ||
try { | ||
var _this7 = this; | ||
return Promise.resolve(_this7._docClient.setDoc(change)); | ||
var m = new MapClient(_this8.checkpoint.maps[name] || {}, _this8.roomID, _this8.docID, name, _this8.ws); | ||
return Promise.resolve(m); | ||
} catch (e) { | ||
@@ -950,62 +789,56 @@ return Promise.reject(e); | ||
_proto.onSetDoc = function onSetDoc(callback) { | ||
this._docClient.onSetDoc(callback); | ||
}; | ||
_proto.onUpdate = function onUpdate(obj, onChangeFn) { | ||
var _this9 = this; | ||
_proto.undo = function undo() { | ||
return this._docClient.undo(); | ||
}; | ||
var bound = this.ws.bind('doc:fwd', function (body) { | ||
if (body.room !== _this9.roomID) return; | ||
_proto.redo = function redo() { | ||
return this._docClient.redo(); | ||
} // Presence | ||
; | ||
if (!body.args || body.args.length < 3) { | ||
// Potentially a network failure, we don't want to crash, | ||
// but do want to warn people | ||
console.error('Unexpected command: ', body.args); | ||
return; | ||
} // Ignore validated commands | ||
_proto.setPresence = function setPresence(key, value) { | ||
this._presenceClient.setPresence(key, value); | ||
if (body.from === _this9.actor) return; | ||
var _ref = [body.args[1], body.args[2]], | ||
docID = _ref[0], | ||
objID = _ref[1]; | ||
if (docID !== _this9.docID) return; | ||
if (objID !== obj.id) return; | ||
onChangeFn(body.args, body.from); | ||
}); | ||
return bound; | ||
}; | ||
_proto.onSetPresence = function onSetPresence(callback) { | ||
this._presenceClient.onSetPresence(callback); | ||
_proto.off = function off(listener) { | ||
this.ws.unbind('doc:fwd', listener); | ||
}; | ||
_createClass(RoomClient, [{ | ||
key: "_socketURL", | ||
set: function set(url) { | ||
this._docClient._socketURL = url; | ||
this._presenceClient._socketURL = url; | ||
} | ||
}]); | ||
return RoomClient; | ||
}(); | ||
var RoomServiceClient = /*#__PURE__*/function () { | ||
function RoomServiceClient(parameters) { | ||
this._roomPool = {}; | ||
this._authorizationUrl = parameters.authUrl; | ||
this._headers = parameters.headers; | ||
var RoomService = /*#__PURE__*/function () { | ||
function RoomService(params) { | ||
this.authURL = params.authURL; | ||
} | ||
var _proto = RoomServiceClient.prototype; | ||
var _proto = RoomService.prototype; | ||
_proto.room = function room(roomReference, defaultDoc) { | ||
if (this._roomPool[roomReference]) { | ||
return this._roomPool[roomReference]; | ||
_proto.room = function room(name) { | ||
try { | ||
var _this2 = this; | ||
var ws = new WebSocket(WS_URL); | ||
return Promise.resolve(createRoom(ws, DOCS_URL, _this2.authURL, name, 'default')); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
var room = new RoomClient({ | ||
authUrl: this._authorizationUrl, | ||
roomReference: roomReference, | ||
defaultDoc: defaultDoc, | ||
headers: this._headers | ||
}); | ||
this._roomPool[roomReference] = room; | ||
return room; | ||
}; | ||
return RoomServiceClient; | ||
return RoomService; | ||
}(); | ||
export default RoomServiceClient; | ||
export { RoomService }; | ||
//# sourceMappingURL=browser.esm.js.map |
@@ -1,1 +0,2 @@ | ||
export declare const ROOM_SERICE_CLIENT_URL = "https://aws.roomservice.dev"; | ||
export declare const WS_URL = "wss://super.roomservice.dev/ws"; | ||
export declare const DOCS_URL = "https://super.roomservice.dev"; |
@@ -1,2 +0,2 @@ | ||
import RoomServiceClient from './client'; | ||
export default RoomServiceClient; | ||
import { RoomService } from './RoomServiceClient'; | ||
export { RoomService }; |
@@ -1,10 +0,87 @@ | ||
export declare type Obj = { | ||
[key: string]: any; | ||
import ReverseTree from './ReverseTree'; | ||
export interface Ref { | ||
type: 'map' | 'list'; | ||
ref: string; | ||
} | ||
export interface Tombstone { | ||
t: ''; | ||
} | ||
export declare type NodeValue = string | Ref | Tombstone; | ||
export interface ListCheckpoint { | ||
afters: string[]; | ||
ids: string[]; | ||
values: string[]; | ||
} | ||
export declare type MapCheckpoint = { | ||
[key: string]: NodeValue; | ||
}; | ||
export interface Room { | ||
reference: string; | ||
export interface DocumentCheckpoint { | ||
id: string; | ||
index: number; | ||
api_version: number; | ||
actors: { | ||
[key: number]: string; | ||
}; | ||
lists: { | ||
[key: string]: ListCheckpoint; | ||
}; | ||
maps: { | ||
[key: string]: MapCheckpoint; | ||
}; | ||
} | ||
export interface Session { | ||
token: string; | ||
export interface Message<T> { | ||
ref: string; | ||
type: string; | ||
version: number; | ||
body: T; | ||
} | ||
export interface FwdResponse { | ||
version: number; | ||
type: 'fwd'; | ||
body: { | ||
from: string; | ||
room: string; | ||
args: string[]; | ||
}; | ||
} | ||
export interface ErrorResponse { | ||
version: number; | ||
type: 'error'; | ||
body: { | ||
request: string; | ||
message: string; | ||
}; | ||
} | ||
export declare type Response = ErrorResponse | FwdResponse; | ||
export interface Document { | ||
lists: { | ||
[key: string]: ReverseTree; | ||
}; | ||
maps: { | ||
[key: string]: { | ||
[key: string]: any; | ||
}; | ||
}; | ||
localIndex: number; | ||
} | ||
export interface WebSocketLikeConnection { | ||
onmessage: (ev: MessageEvent) => any; | ||
send: (data: any) => any; | ||
} | ||
export interface DocumentContext { | ||
lists: { | ||
[key: string]: ReverseTree; | ||
}; | ||
maps: { | ||
[key: string]: { | ||
[key: string]: any; | ||
}; | ||
}; | ||
localIndex: number; | ||
actor: string; | ||
id: string; | ||
} | ||
export declare type Prop<V, K extends keyof V> = V[K]; | ||
export interface ObjectClient { | ||
id: string; | ||
} |
{ | ||
"version": "0.8.0", | ||
"version": "1.0.0-1", | ||
"license": "MIT", | ||
@@ -18,7 +18,2 @@ "main": "dist/index.js", | ||
"peerDependencies": {}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "tsdx lint" | ||
} | ||
}, | ||
"prettier": { | ||
@@ -36,3 +31,2 @@ "printWidth": 80, | ||
"@types/jest": "^25.1.3", | ||
"@types/lodash": "^4.14.149", | ||
"@types/node": "^12.12.17", | ||
@@ -42,5 +36,3 @@ "@types/safe-json-stringify": "^1.1.0", | ||
"@types/uuid": "^3.4.6", | ||
"husky": "^4.2.3", | ||
"jest": "^24.9.0", | ||
"nock": "^11.7.0", | ||
"ts-jest": "^24.2.0", | ||
@@ -52,15 +44,4 @@ "tsdx": "^0.12.3", | ||
"dependencies": { | ||
"automerge": "0.13.0", | ||
"idb-keyval": "^3.2.0", | ||
"immutable": "^3.8.2", | ||
"invariant": "^2.2.4", | ||
"ky": "^0.16.1", | ||
"ky-universal": "^0.3.0", | ||
"lodash": "^4.17.15", | ||
"lodash-es": "^4.17.15", | ||
"manymerge": "2.5.3", | ||
"safe-json-stringify": "^1.2.0", | ||
"socket.io-client": "^2.3.0", | ||
"uuid": "^3.3.3" | ||
"tiny-invariant": "^1.1.0" | ||
} | ||
} |
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
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
1
11
27
4
179556
1698
7
+ Addedtiny-invariant@^1.1.0
+ Addedtiny-invariant@1.3.3(transitive)
- Removedautomerge@0.13.0
- Removedidb-keyval@^3.2.0
- Removedimmutable@^3.8.2
- Removedinvariant@^2.2.4
- Removedky@^0.16.1
- Removedky-universal@^0.3.0
- Removedlodash@^4.17.15
- Removedlodash-es@^4.17.15
- Removedmanymerge@2.5.3
- Removedsafe-json-stringify@^1.2.0
- Removedsocket.io-client@^2.3.0
- Removeduuid@^3.3.3
- Removed@types/events@3.0.3(transitive)
- Removedabort-controller@3.0.0(transitive)
- Removedafter@0.8.2(transitive)
- Removedarraybuffer.slice@0.0.7(transitive)
- Removedautomerge@0.13.0(transitive)
- Removedautomerge-clocks@1.2.1(transitive)
- Removedbacko2@1.0.2(transitive)
- Removedbase64-arraybuffer@0.1.4(transitive)
- Removedblob@0.0.5(transitive)
- Removedcomponent-bind@1.0.0(transitive)
- Removedcomponent-emitter@1.3.1(transitive)
- Removedcomponent-inherit@0.0.3(transitive)
- Removeddebug@3.1.0(transitive)
- Removedengine.io-client@3.5.4(transitive)
- Removedengine.io-parser@2.2.1(transitive)
- Removedevent-target-shim@5.0.1(transitive)
- Removedhas-binary2@1.0.3(transitive)
- Removedhas-cors@1.1.0(transitive)
- Removedidb-keyval@3.2.0(transitive)
- Removedimmutable@3.8.2(transitive)
- Removedindexof@0.0.1(transitive)
- Removedinvariant@2.2.4(transitive)
- Removedisarray@2.0.1(transitive)
- Removedjs-tokens@4.0.0(transitive)
- Removedky@0.16.2(transitive)
- Removedky-universal@0.3.0(transitive)
- Removedlodash@4.17.21(transitive)
- Removedlodash-es@4.17.21(transitive)
- Removedloose-envify@1.4.0(transitive)
- Removedmanymerge@2.5.3(transitive)
- Removedms@2.0.0(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removedparseqs@0.0.6(transitive)
- Removedparseuri@0.0.6(transitive)
- Removedsafe-json-stringify@1.2.0(transitive)
- Removedsocket.io-client@2.5.0(transitive)
- Removedsocket.io-parser@3.3.4(transitive)
- Removedto-array@0.1.4(transitive)
- Removedtr46@0.0.3(transitive)
- Removedtransit-immutable-js@0.7.0(transitive)
- Removedtransit-js@0.8.874(transitive)
- Removeduuid@3.3.23.4.0(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)
- Removedws@7.5.10(transitive)
- Removedxmlhttprequest-ssl@1.6.3(transitive)
- Removedyeast@0.1.2(transitive)