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

@hocuspocus/server

Package Overview
Dependencies
Maintainers
3
Versions
116
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hocuspocus/server - npm Package Compare versions

Comparing version 1.0.0-alpha.65 to 1.0.0-alpha.66

dist/packages/server/src/Debugger.d.ts

8

CHANGELOG.md

@@ -6,2 +6,10 @@ # Change Log

# [1.0.0-alpha.66](https://github.com/ueberdosis/hocuspocus/compare/@hocuspocus/server@1.0.0-alpha.65...@hocuspocus/server@1.0.0-alpha.66) (2021-08-31)
**Note:** Version bump only for package @hocuspocus/server
# [1.0.0-alpha.65](https://github.com/ueberdosis/hocuspocus/compare/@hocuspocus/server@1.0.0-alpha.64...@hocuspocus/server@1.0.0-alpha.65) (2021-08-29)

@@ -8,0 +16,0 @@

254

dist/hocuspocus-server.esm.js

@@ -428,4 +428,10 @@ import WebSocket from 'ws';

})(MessageType || (MessageType = {}));
/**
* State of the WebSocket connection.
* https://developer.mozilla.org/de/docs/Web/API/WebSocket/readyState
*/
var WsReadyStates;
(function (WsReadyStates) {
WsReadyStates[WsReadyStates["Connecting"] = 0] = "Connecting";
WsReadyStates[WsReadyStates["Open"] = 1] = "Open";
WsReadyStates[WsReadyStates["Closing"] = 2] = "Closing";

@@ -1095,2 +1101,3 @@ WsReadyStates[WsReadyStates["Closed"] = 3] = "Closed";

createSyncMessage() {
this.type = MessageType.Sync;
writeVarUint(this.encoder, MessageType.Sync);

@@ -1100,2 +1107,4 @@ return this;

createAwarenessUpdateMessage(awareness, changedClients) {
this.type = MessageType.Awareness;
this.category = 'Update';
const message = encodeAwarenessUpdate(awareness, changedClients || Array.from(awareness.getStates().keys()));

@@ -1107,2 +1116,4 @@ writeVarUint(this.encoder, MessageType.Awareness);

writeAuthenticated() {
this.type = MessageType.Auth;
this.category = 'Authenticated';
writeVarUint(this.encoder, MessageType.Auth);

@@ -1113,2 +1124,4 @@ writeAuthenticated(this.encoder);

writePermissionDenied(reason) {
this.type = MessageType.Auth;
this.category = 'PermissionDenied';
writeVarUint(this.encoder, MessageType.Auth);

@@ -1119,2 +1132,3 @@ writePermissionDenied(this.encoder, reason);

writeFirstSyncStepFor(document) {
this.category = 'SyncStep1';
writeSyncStep1(this.encoder, document);

@@ -1124,2 +1138,3 @@ return this;

writeUpdate(update) {
this.category = 'Update';
writeUpdate(this.encoder, update);

@@ -1133,2 +1148,48 @@ return this;

// import * as time from 'lib0/time'
class MessageLogger {
constructor() {
this.logs = [];
this.listen = false;
this.output = false;
}
enable() {
this.listen = true;
}
disable() {
this.listen = false;
}
verbose() {
this.output = true;
}
quiet() {
this.output = false;
}
log(message) {
if (!this.listen) {
return this;
}
const item = {
...message,
type: MessageType[message.type],
// time: time.getUnixTime(),
};
this.logs.push(item);
if (this.output) {
console.log('[DEBUGGER]', item.direction === 'in' ? '–>' : '<–', `${item.type}/${item.category}`);
}
return this;
}
flush() {
this.logs = [];
return this;
}
get() {
return {
logs: this.logs,
};
}
}
const Debugger = new MessageLogger();
class Document extends Doc {

@@ -1145,2 +1206,3 @@ /**

this.connections = new Map();
this.debugger = Debugger;
this.name = name;

@@ -1181,3 +1243,3 @@ this.mux = createMutex();

addConnection(connection) {
this.connections.set(connection.instance, {
this.connections.set(connection.webSocket, {
clients: new Set(),

@@ -1192,3 +1254,3 @@ connection,

hasConnection(connection) {
return this.connections.has(connection.instance);
return this.connections.has(connection.webSocket);
}

@@ -1199,4 +1261,4 @@ /**

removeConnection(connection) {
removeAwarenessStates(this.awareness, Array.from(this.getClients(connection.instance)), null);
this.connections.delete(connection.instance);
removeAwarenessStates(this.awareness, Array.from(this.getClients(connection.webSocket)), null);
this.connections.delete(connection.webSocket);
return this;

@@ -1233,3 +1295,3 @@ }

applyAwarenessUpdate(connection, update) {
applyAwarenessUpdate(this.awareness, update, connection.instance);
applyAwarenessUpdate(this.awareness, update, connection.webSocket);
return this;

@@ -1250,5 +1312,12 @@ }

}
this.getConnections().forEach(connection => connection.send(new OutgoingMessage()
.createAwarenessUpdateMessage(this.awareness, changedClients)
.toUint8Array()));
this.getConnections().forEach(connection => {
const awarenessMessage = new OutgoingMessage()
.createAwarenessUpdateMessage(this.awareness, changedClients);
this.debugger.log({
direction: 'out',
type: awarenessMessage.type,
category: awarenessMessage.category,
});
connection.send(awarenessMessage.toUint8Array());
});
return this;

@@ -1264,3 +1333,10 @@ }

.writeUpdate(update);
this.getConnections().forEach(connection => connection.send(message.toUint8Array()));
this.getConnections().forEach(connection => {
this.debugger.log({
direction: 'out',
type: message.type,
category: message.category,
});
connection.send(message.toUint8Array());
});
return this;

@@ -1297,2 +1373,3 @@ }

constructor(message) {
this.debugger = Debugger;
this.message = message;

@@ -1313,2 +1390,7 @@ }

case MessageType.Awareness:
this.debugger.log({
direction: 'in',
type,
category: 'Update',
});
applyAwarenessUpdate(document.awareness, message.readVarUint8Array(), connection);

@@ -1324,5 +1406,15 @@ break;

case messageYjsSyncStep1:
this.debugger.log({
direction: 'in',
type,
category: 'SyncStep1',
});
readSyncStep1(message.decoder, message.encoder, document);
break;
case messageYjsSyncStep2:
this.debugger.log({
direction: 'in',
type,
category: 'SyncStep2',
});
if (connection === null || connection === void 0 ? void 0 : connection.readOnly) {

@@ -1334,2 +1426,7 @@ break;

case messageYjsUpdate:
this.debugger.log({
direction: 'in',
type,
category: 'Update',
});
if (connection === null || connection === void 0 ? void 0 : connection.readOnly) {

@@ -1356,3 +1453,4 @@ break;

};
this.connection = connection;
this.debugger = Debugger;
this.webSocket = connection;
this.context = context;

@@ -1365,8 +1463,8 @@ this.document = document;

this.lock = new AsyncLock();
this.connection.binaryType = 'arraybuffer';
this.webSocket.binaryType = 'arraybuffer';
this.document.addConnection(this);
this.pingInterval = setInterval(this.check.bind(this), this.timeout);
this.connection.on('close', this.close.bind(this));
this.connection.on('message', this.handleMessage.bind(this));
this.connection.on('pong', () => { this.pongReceived = true; });
this.webSocket.on('close', this.close.bind(this));
this.webSocket.on('message', this.handleMessage.bind(this));
this.webSocket.on('pong', () => { this.pongReceived = true; });
this.sendFirstSyncStep();

@@ -1385,8 +1483,8 @@ }

send(message) {
if (this.connection.readyState === WsReadyStates.Closing
|| this.connection.readyState === WsReadyStates.Closed) {
if (this.webSocket.readyState === WsReadyStates.Closing
|| this.webSocket.readyState === WsReadyStates.Closed) {
this.close();
}
try {
this.connection.send(message, (error) => {
this.webSocket.send(message, (error) => {
if (error != null)

@@ -1401,5 +1499,5 @@ this.close();

/**
* Close the connection
* Graceful wrapper around the WebSocket close method.
*/
close() {
close(event) {
this.lock.acquire('close', (done) => {

@@ -1414,3 +1512,3 @@ if (this.pingInterval) {

this.callbacks.onClose(this.document);
this.connection.close();
this.webSocket.close(event === null || event === void 0 ? void 0 : event.code, event === null || event === void 0 ? void 0 : event.reason);
done();

@@ -1430,3 +1528,3 @@ });

try {
this.connection.ping();
this.webSocket.ping();
}

@@ -1443,12 +1541,22 @@ catch (exception) {

sendFirstSyncStep() {
this.send(new OutgoingMessage()
const syncMessage = new OutgoingMessage()
.createSyncMessage()
.writeFirstSyncStepFor(this.document)
.toUint8Array());
.writeFirstSyncStepFor(this.document);
this.debugger.log({
direction: 'out',
type: syncMessage.type,
category: syncMessage.category,
});
this.send(syncMessage.toUint8Array());
if (!this.document.hasAwarenessStates()) {
return;
}
this.send(new OutgoingMessage()
.createAwarenessUpdateMessage(this.document.awareness)
.toUint8Array());
const awarenessMessage = new OutgoingMessage()
.createAwarenessUpdateMessage(this.document.awareness);
this.debugger.log({
direction: 'out',
type: awarenessMessage.type,
category: awarenessMessage.category,
});
this.send(awarenessMessage.toUint8Array());
}

@@ -1464,6 +1572,16 @@ /**

* Get the underlying connection instance
* @deprecated
*/
get instance() {
return this.connection;
console.warn('connection.instance is deprecated, use `connection.webSocket` instead.');
return this.webSocket;
}
/**
* Get the underlying connection instance
* @deprecated
*/
get connection() {
console.warn('connection.connection is deprecated, use `connection.webSocket` instead.');
return this.webSocket;
}
}

@@ -1475,6 +1593,10 @@

};
const ResetConnection = {
code: 4205,
reason: 'Reset Connection',
};
var name = "@hocuspocus/server";
var description = "plug & play collaboration backend";
var version = "1.0.0-alpha.64";
var version = "1.0.0-alpha.65";
var homepage = "https://hocuspocus.dev";

@@ -1551,2 +1673,3 @@ var keywords = [

this.documents = new Map();
this.debugger = Debugger;
}

@@ -1577,2 +1700,3 @@ /**

yjsVersion: null,
instance: this,
});

@@ -1590,8 +1714,8 @@ return this;

async listen() {
const websocketServer = new WebSocket.Server({ noServer: true });
websocketServer.on('connection', (incoming, request) => {
const webSocketServer = new WebSocket.Server({ noServer: true });
webSocketServer.on('connection', (incoming, request) => {
this.handleConnection(incoming, request, Hocuspocus.getDocumentName(request));
});
const server = createServer((request, response) => {
this.hooks('onRequest', { request, response })
this.hooks('onRequest', { request, response, instance: this })
.then(() => {

@@ -1618,4 +1742,4 @@ // default response if all prior hooks don't interfere

// @ts-ignore
websocketServer.handleUpgrade(request, socket, head, ws => {
websocketServer.emit('connection', ws, request);
webSocketServer.handleUpgrade(request, socket, head, ws => {
webSocketServer.emit('connection', ws, request);
});

@@ -1633,3 +1757,3 @@ })

this.httpServer = server;
this.websocketServer = websocketServer;
this.webSocketServer = webSocketServer;
await new Promise((resolve, reject) => {

@@ -1644,2 +1768,19 @@ server.listen(this.configuration.port, () => {

/**
* Force closes one or more connections
*/
closeConnections(documentName) {
// Iterate through all connections for all documents
// and invoke their close method, which is a graceful
// disconnect wrapper around the underlying websocket.close
this.documents.forEach((document) => {
// If a documentName was specified, bail if it doesnt match
if (documentName && document.name !== documentName) {
return;
}
document.connections.forEach(({ connection } = { connection: Connection }) => {
connection.close(ResetConnection);
});
});
}
/**
* Destroy the server

@@ -1650,3 +1791,3 @@ */

(_a = this.httpServer) === null || _a === void 0 ? void 0 : _a.close();
(_b = this.websocketServer) === null || _b === void 0 ? void 0 : _b.close();
(_b = this.webSocketServer) === null || _b === void 0 ? void 0 : _b.close();
await this.hooks('onDestroy', {});

@@ -1663,2 +1804,3 @@ }

documentName,
instance: this,
request,

@@ -1676,3 +1818,3 @@ requestHeaders: request.headers,

// if no hook interrupts create a document and connection
this.createDocument(documentName, request, socketId, context).then(document => {
this.createDocument(documentName, request, socketId, connection, context).then(document => {
this.createConnection(incoming, request, document, socketId, connection.readOnly, context);

@@ -1697,2 +1839,7 @@ // Remove the queue listener

const token = readVarString(decoder);
this.debugger.log({
direction: 'in',
type,
category: 'Token',
});
this.hooks('onAuthenticate', { token, ...hookPayload }, (contextAdditions) => {

@@ -1705,2 +1852,7 @@ // merge context from hook

const message = new OutgoingMessage().writeAuthenticated();
this.debugger.log({
direction: 'out',
type: message.type,
category: message.category,
});
incoming.send(message.toUint8Array());

@@ -1714,2 +1866,7 @@ })

const message = new OutgoingMessage().writePermissionDenied('permission-denied');
this.debugger.log({
direction: 'out',
type: message.type,
category: message.category,
});
// Ensure that the permission denied message is sent before the

@@ -1763,3 +1920,3 @@ // connection is closed

*/
async createDocument(documentName, request, socketId, context) {
async createDocument(documentName, request, socketId, connection, context) {
if (this.documents.has(documentName)) {

@@ -1773,2 +1930,3 @@ const document = this.documents.get(documentName);

context,
connection,
document,

@@ -1862,2 +2020,22 @@ documentName,

}
enableDebugging() {
this.debugger.enable();
}
enableLogging() {
this.debugger.verbose();
}
disableLogging() {
this.debugger.quiet();
}
disableDebugging() {
this.debugger.disable();
}
flushMessageLogs() {
this.debugger.flush();
return this;
}
getMessageLogs() {
var _a;
return (_a = this.debugger.get()) === null || _a === void 0 ? void 0 : _a.logs;
}
}

@@ -1864,0 +2042,0 @@ const Server = new Hocuspocus();

import { CloseEvent } from './types';
export declare const Forbidden: CloseEvent;
export declare const ResetConnection: CloseEvent;
export declare const CloseEvents: CloseEvent[];

15

dist/packages/server/src/Connection.d.ts

@@ -6,4 +6,6 @@ /// <reference types="node" />

import Document from './Document';
import { CloseEvent } from './types';
import { MessageLogger } from './Debugger';
declare class Connection {
connection: WebSocket;
webSocket: WebSocket;
context: any;

@@ -19,2 +21,3 @@ document: Document;

readOnly: Boolean;
debugger: MessageLogger;
/**

@@ -33,5 +36,5 @@ * Constructor.

/**
* Close the connection
* Graceful wrapper around the WebSocket close method.
*/
close(): void;
close(event?: CloseEvent): void;
/**

@@ -54,5 +57,11 @@ * Check if pong was received and close the connection otherwise

* Get the underlying connection instance
* @deprecated
*/
get instance(): WebSocket;
/**
* Get the underlying connection instance
* @deprecated
*/
get connection(): WebSocket;
}
export default Connection;

@@ -6,2 +6,3 @@ import WebSocket from 'ws';

import Connection from './Connection';
import { MessageLogger } from './Debugger';
declare class Document extends Doc {

@@ -15,2 +16,3 @@ awareness: Awareness;

mux: mutex;
debugger: MessageLogger;
/**

@@ -17,0 +19,0 @@ * Constructor.

@@ -5,2 +5,3 @@ /// <reference types="node" />

import { Configuration } from './types';
import { MessageLogger } from './Debugger';
export declare const defaultConfiguration: {

@@ -17,3 +18,4 @@ port: number;

httpServer?: HTTPServer;
websocketServer?: WebSocket.Server;
webSocketServer?: WebSocket.Server;
debugger: MessageLogger;
/**

@@ -29,2 +31,6 @@ * Configure the server

/**
* Force closes one or more connections
*/
closeConnections(documentName?: string): void;
/**
* Destroy the server

@@ -68,3 +74,9 @@ */

private static getDocumentName;
enableDebugging(): void;
enableLogging(): void;
disableLogging(): void;
disableDebugging(): void;
flushMessageLogs(): this;
getMessageLogs(): any[];
}
export declare const Server: Hocuspocus;
import Connection from './Connection';
import { IncomingMessage } from './IncomingMessage';
import { MessageLogger } from './Debugger';
export declare class MessageReceiver {
message: IncomingMessage;
debugger: MessageLogger;
constructor(message: IncomingMessage);

@@ -6,0 +8,0 @@ apply(connection: Connection): void;

@@ -6,2 +6,4 @@ import { Encoder } from 'lib0/encoding';

encoder: Encoder;
type?: number;
category?: string;
constructor();

@@ -8,0 +10,0 @@ createSyncMessage(): OutgoingMessage;

@@ -6,2 +6,3 @@ /// <reference types="node" />

import Document from './Document';
import { Hocuspocus } from './Hocuspocus';
export declare enum MessageType {

@@ -13,3 +14,9 @@ Unknown = -1,

}
/**
* State of the WebSocket connection.
* https://developer.mozilla.org/de/docs/Web/API/WebSocket/readyState
*/
export declare enum WsReadyStates {
Connecting = 0,
Open = 1,
Closing = 2,

@@ -23,2 +30,6 @@ Closed = 3

}
export interface ConnectionConfig {
readOnly: boolean;
isAuthenticated: boolean;
}
export interface Extension {

@@ -37,4 +48,13 @@ onAuthenticate?(data: onAuthenticatePayload): Promise<any>;

export interface Configuration extends Extension {
/**
* A list of hocuspocus extenions.
*/
extensions: Array<Extension>;
/**
* The port which the server listens on.
*/
port: number | null;
/**
* Defines in which interval the server sends a ping, and closes the connection when no pong is sent back.
*/
timeout: number;

@@ -44,2 +64,3 @@ }

documentName: string;
instance: Hocuspocus;
requestHeaders: IncomingHttpHeaders;

@@ -49,8 +70,7 @@ requestParameters: URLSearchParams;

token: string;
connection: {
readOnly: boolean;
};
connection: ConnectionConfig;
}
export interface onConnectPayload {
documentName: string;
instance: Hocuspocus;
request: IncomingMessage;

@@ -60,5 +80,3 @@ requestHeaders: IncomingHttpHeaders;

socketId: string;
connection: {
readOnly: boolean;
};
connection: ConnectionConfig;
}

@@ -72,2 +90,3 @@ export interface onCreateDocumentPayload {

socketId: string;
connection: ConnectionConfig;
}

@@ -96,2 +115,3 @@ export interface onChangePayload {

response: ServerResponse;
instance: Hocuspocus;
}

@@ -102,2 +122,3 @@ export interface onUpgradePayload {

socket: Socket;
instance: Hocuspocus;
}

@@ -113,2 +134,3 @@ export interface onListenPayload {

yjsVersion: string;
instance: Hocuspocus;
}

@@ -115,0 +137,0 @@ export interface CloseEvent {

{
"name": "@hocuspocus/server",
"description": "plug & play collaboration backend",
"version": "1.0.0-alpha.65",
"version": "1.0.0-alpha.66",
"homepage": "https://hocuspocus.dev",

@@ -35,3 +35,3 @@ "keywords": [

},
"gitHead": "b0e575f7941fc52e0d11f28c61af5dd024caa981"
"gitHead": "f4b2c62f0322daa4c95f6be2009fc7a9c48557cb"
}

@@ -8,4 +8,10 @@ import { CloseEvent } from './types'

export const ResetConnection: CloseEvent = {
code: 4205,
reason: 'Reset Connection',
}
export const CloseEvents: CloseEvent[] = [
Forbidden,
ResetConnection,
]

@@ -7,9 +7,10 @@ import AsyncLock from 'async-lock'

import { IncomingMessage } from './IncomingMessage'
import { WsReadyStates } from './types'
import { CloseEvent, WsReadyStates } from './types'
import { OutgoingMessage } from './OutgoingMessage'
import { MessageReceiver } from './MessageReceiver'
import { Debugger, MessageLogger } from './Debugger'
class Connection {
connection: WebSocket
webSocket: WebSocket

@@ -38,2 +39,4 @@ context: any

debugger: MessageLogger = Debugger
/**

@@ -51,3 +54,3 @@ * Constructor.

) {
this.connection = connection
this.webSocket = connection
this.context = context

@@ -62,3 +65,3 @@ this.document = document

this.connection.binaryType = 'arraybuffer'
this.webSocket.binaryType = 'arraybuffer'
this.document.addConnection(this)

@@ -68,5 +71,5 @@

this.connection.on('close', this.close.bind(this))
this.connection.on('message', this.handleMessage.bind(this))
this.connection.on('pong', () => { this.pongReceived = true })
this.webSocket.on('close', this.close.bind(this))
this.webSocket.on('message', this.handleMessage.bind(this))
this.webSocket.on('pong', () => { this.pongReceived = true })

@@ -90,4 +93,4 @@ this.sendFirstSyncStep()

if (
this.connection.readyState === WsReadyStates.Closing
|| this.connection.readyState === WsReadyStates.Closed
this.webSocket.readyState === WsReadyStates.Closing
|| this.webSocket.readyState === WsReadyStates.Closed
) {

@@ -98,3 +101,3 @@ this.close()

try {
this.connection.send(message, (error: any) => {
this.webSocket.send(message, (error: any) => {
if (error != null) this.close()

@@ -108,5 +111,5 @@ })

/**
* Close the connection
* Graceful wrapper around the WebSocket close method.
*/
close(): void {
close(event?: CloseEvent): void {
this.lock.acquire('close', (done: Function) => {

@@ -124,3 +127,3 @@

this.callbacks.onClose(this.document)
this.connection.close()
this.webSocket.close(event?.code, event?.reason)

@@ -145,3 +148,3 @@ done()

try {
this.connection.ping()
this.webSocket.ping()
} catch (exception) {

@@ -158,9 +161,14 @@ this.close()

private sendFirstSyncStep(): void {
this.send(
new OutgoingMessage()
.createSyncMessage()
.writeFirstSyncStepFor(this.document)
.toUint8Array(),
)
const syncMessage = new OutgoingMessage()
.createSyncMessage()
.writeFirstSyncStepFor(this.document)
this.debugger.log({
direction: 'out',
type: syncMessage.type,
category: syncMessage.category,
})
this.send(syncMessage.toUint8Array())
if (!this.document.hasAwarenessStates()) {

@@ -170,7 +178,12 @@ return

this.send(
new OutgoingMessage()
.createAwarenessUpdateMessage(this.document.awareness)
.toUint8Array(),
)
const awarenessMessage = new OutgoingMessage()
.createAwarenessUpdateMessage(this.document.awareness)
this.debugger.log({
direction: 'out',
type: awarenessMessage.type,
category: awarenessMessage.category,
})
this.send(awarenessMessage.toUint8Array())
}

@@ -190,8 +203,21 @@

* Get the underlying connection instance
* @deprecated
*/
get instance(): WebSocket {
return this.connection
console.warn('connection.instance is deprecated, use `connection.webSocket` instead.')
return this.webSocket
}
/**
* Get the underlying connection instance
* @deprecated
*/
public get connection(): WebSocket {
console.warn('connection.connection is deprecated, use `connection.webSocket` instead.')
return this.webSocket
}
}
export default Connection

@@ -5,6 +5,6 @@ import WebSocket from 'ws'

import { mutex, createMutex } from 'lib0/mutex.js'
import { AwarenessUpdate } from './types'
import Connection from './Connection'
import { OutgoingMessage } from './OutgoingMessage'
import { Debugger, MessageLogger } from './Debugger'

@@ -26,2 +26,4 @@ class Document extends Doc {

debugger: MessageLogger = Debugger
/**

@@ -76,3 +78,3 @@ * Constructor.

addConnection(connection: Connection): Document {
this.connections.set(connection.instance, {
this.connections.set(connection.webSocket, {
clients: new Set(),

@@ -89,3 +91,3 @@ connection,

hasConnection(connection: Connection): boolean {
return this.connections.has(connection.instance)
return this.connections.has(connection.webSocket)
}

@@ -99,7 +101,7 @@

this.awareness,
Array.from(this.getClients(connection.instance)),
Array.from(this.getClients(connection.webSocket)),
null,
)
this.connections.delete(connection.instance)
this.connections.delete(connection.webSocket)

@@ -146,3 +148,3 @@ return this

update,
connection.instance,
connection.webSocket,
)

@@ -172,8 +174,17 @@

this.getConnections().forEach(connection => connection.send(
new OutgoingMessage()
this.getConnections().forEach(connection => {
const awarenessMessage = new OutgoingMessage()
.createAwarenessUpdateMessage(this.awareness, changedClients)
.toUint8Array(),
))
this.debugger.log({
direction: 'out',
type: awarenessMessage.type,
category: awarenessMessage.category,
})
connection.send(
awarenessMessage.toUint8Array(),
)
})
return this

@@ -192,6 +203,14 @@ }

this.getConnections().forEach(connection => connection.send(
message.toUint8Array(),
))
this.getConnections().forEach(connection => {
this.debugger.log({
direction: 'out',
type: message.type,
category: message.category,
})
connection.send(
message.toUint8Array(),
)
})
return this

@@ -198,0 +217,0 @@ }

@@ -7,9 +7,9 @@ import * as decoding from 'lib0/decoding'

import { v4 as uuid } from 'uuid'
import { MessageType, Configuration } from './types'
import { MessageType, Configuration, ConnectionConfig } from './types'
import Document from './Document'
import Connection from './Connection'
import { Forbidden } from './CloseEvents'
import { Forbidden, ResetConnection } from './CloseEvents'
import { OutgoingMessage } from './OutgoingMessage'
import packageJson from '../package.json'
import { Debugger, MessageLogger } from './Debugger'

@@ -25,3 +25,2 @@ export const defaultConfiguration = {

export class Hocuspocus {
configuration: Configuration = {

@@ -45,4 +44,6 @@ ...defaultConfiguration,

websocketServer?: WebSocket.Server
webSocketServer?: WebSocket.Server
debugger: MessageLogger = Debugger
/**

@@ -52,3 +53,2 @@ * Configure the server

configure(configuration: Partial<Configuration>): Hocuspocus {
this.configuration = {

@@ -76,6 +76,6 @@ ...this.configuration,

yjsVersion: null,
instance: this,
})
return this
}

@@ -93,5 +93,4 @@

async listen(): Promise<void> {
const websocketServer = new WebSocket.Server({ noServer: true })
websocketServer.on('connection', (incoming: WebSocket, request: IncomingMessage) => {
const webSocketServer = new WebSocket.Server({ noServer: true })
webSocketServer.on('connection', (incoming: WebSocket, request: IncomingMessage) => {
this.handleConnection(incoming, request, Hocuspocus.getDocumentName(request))

@@ -101,3 +100,3 @@ })

const server = createServer((request, response) => {
this.hooks('onRequest', { request, response })
this.hooks('onRequest', { request, response, instance: this })
.then(() => {

@@ -124,4 +123,4 @@ // default response if all prior hooks don't interfere

// @ts-ignore
websocketServer.handleUpgrade(request, socket, head, ws => {
websocketServer.emit('connection', ws, request)
webSocketServer.handleUpgrade(request, socket, head, ws => {
webSocketServer.emit('connection', ws, request)
})

@@ -139,3 +138,3 @@ })

this.httpServer = server
this.websocketServer = websocketServer
this.webSocketServer = webSocketServer

@@ -149,3 +148,21 @@ await new Promise((resolve: Function, reject: Function) => {

})
}
/**
* Force closes one or more connections
*/
closeConnections(documentName?: string) {
// Iterate through all connections for all documents
// and invoke their close method, which is a graceful
// disconnect wrapper around the underlying websocket.close
this.documents.forEach((document: Document) => {
// If a documentName was specified, bail if it doesnt match
if (documentName && document.name !== documentName) {
return
}
document.connections.forEach(({ connection } = { connection: Connection }) => {
connection.close(ResetConnection)
})
})
}

@@ -157,8 +174,6 @@

async destroy(): Promise<any> {
this.httpServer?.close()
this.websocketServer?.close()
this.webSocketServer?.close()
await this.hooks('onDestroy', {})
}

@@ -170,9 +185,9 @@

handleConnection(incoming: WebSocket, request: IncomingMessage, documentName: string, context: any = null): void {
// create a unique identifier for every socket connection
const socketId = uuid()
const connection = { readOnly: false, isAuthenticated: false }
const connection: ConnectionConfig = { readOnly: false, isAuthenticated: false }
const hookPayload = {
documentName,
instance: this,
request,

@@ -193,3 +208,3 @@ requestHeaders: request.headers,

// if no hook interrupts create a document and connection
this.createDocument(documentName, request, socketId, context).then(document => {
this.createDocument(documentName, request, socketId, connection, context).then(document => {
this.createConnection(incoming, request, document, socketId, connection.readOnly, context)

@@ -219,2 +234,8 @@

this.debugger.log({
direction: 'in',
type,
category: 'Token',
})
this.hooks('onAuthenticate', { token, ...hookPayload }, (contextAdditions: any) => {

@@ -228,2 +249,9 @@ // merge context from hook

const message = new OutgoingMessage().writeAuthenticated()
this.debugger.log({
direction: 'out',
type: message.type,
category: message.category,
})
incoming.send(message.toUint8Array())

@@ -238,2 +266,8 @@ })

this.debugger.log({
direction: 'out',
type: message.type,
category: message.category,
})
// Ensure that the permission denied message is sent before the

@@ -265,3 +299,2 @@ // connection is closed

})
}

@@ -274,3 +307,2 @@

private handleDocumentUpdate(document: Document, connection: Connection, update: Uint8Array, request: IncomingMessage, socketId: string): void {
const hookPayload = {

@@ -290,3 +322,2 @@ clientsCount: document.connectionsCount(),

})
}

@@ -298,3 +329,3 @@

*/
private async createDocument(documentName: string, request: IncomingMessage, socketId: string, context?: any): Promise<Document> {
private async createDocument(documentName: string, request: IncomingMessage, socketId: string, connection: ConnectionConfig, context?: any): Promise<Document> {
if (this.documents.has(documentName)) {

@@ -310,2 +341,3 @@ const document = this.documents.get(documentName)

context,
connection,
document,

@@ -342,3 +374,2 @@ documentName,

private createConnection(connection: WebSocket, request: IncomingMessage, document: Document, socketId: string, readOnly = false, context?: any): Connection {
const instance = new Connection(connection, request, document, this.configuration.timeout, socketId, context, readOnly)

@@ -369,3 +400,2 @@

return instance
}

@@ -420,4 +450,30 @@

}
enableDebugging() {
this.debugger.enable()
}
enableLogging() {
this.debugger.verbose()
}
disableLogging() {
this.debugger.quiet()
}
disableDebugging() {
this.debugger.disable()
}
flushMessageLogs() {
this.debugger.flush()
return this
}
getMessageLogs() {
return this.debugger.get()?.logs
}
}
export const Server = new Hocuspocus()

@@ -13,2 +13,3 @@ import {

import { IncomingMessage } from './IncomingMessage'
import { Debugger, MessageLogger } from './Debugger'

@@ -19,2 +20,4 @@ export class MessageReceiver {

debugger: MessageLogger = Debugger
constructor(message: IncomingMessage) {

@@ -40,2 +43,8 @@ this.message = message

case MessageType.Awareness:
this.debugger.log({
direction: 'in',
type,
category: 'Update',
})
applyAwarenessUpdate(document.awareness, message.readVarUint8Array(), connection)

@@ -55,5 +64,17 @@

case messageYjsSyncStep1:
this.debugger.log({
direction: 'in',
type,
category: 'SyncStep1',
})
readSyncStep1(message.decoder, message.encoder, document)
break
case messageYjsSyncStep2:
this.debugger.log({
direction: 'in',
type,
category: 'SyncStep2',
})
if (connection?.readOnly) {

@@ -66,2 +87,8 @@ break

case messageYjsUpdate:
this.debugger.log({
direction: 'in',
type,
category: 'Update',
})
if (connection?.readOnly) {

@@ -68,0 +95,0 @@ break

@@ -19,2 +19,6 @@ import {

type?: number
category?: string
constructor() {

@@ -25,2 +29,4 @@ this.encoder = createEncoder()

createSyncMessage(): OutgoingMessage {
this.type = MessageType.Sync
writeVarUint(this.encoder, MessageType.Sync)

@@ -32,2 +38,5 @@

createAwarenessUpdateMessage(awareness: Awareness, changedClients?: Array<any>): OutgoingMessage {
this.type = MessageType.Awareness
this.category = 'Update'
const message = encodeAwarenessUpdate(

@@ -45,2 +54,5 @@ awareness,

writeAuthenticated(): OutgoingMessage {
this.type = MessageType.Auth
this.category = 'Authenticated'
writeVarUint(this.encoder, MessageType.Auth)

@@ -53,2 +65,5 @@ writeAuthenticated(this.encoder)

writePermissionDenied(reason: string): OutgoingMessage {
this.type = MessageType.Auth
this.category = 'PermissionDenied'
writeVarUint(this.encoder, MessageType.Auth)

@@ -61,2 +76,4 @@ writePermissionDenied(this.encoder, reason)

writeFirstSyncStepFor(document: Document): OutgoingMessage {
this.category = 'SyncStep1'
writeSyncStep1(this.encoder, document)

@@ -68,2 +85,4 @@

writeUpdate(update: Uint8Array): OutgoingMessage {
this.category = 'Update'
writeUpdate(this.encoder, update)

@@ -70,0 +89,0 @@

@@ -7,2 +7,3 @@ import {

import Document from './Document'
import { Hocuspocus } from './Hocuspocus'

@@ -16,3 +17,9 @@ export enum MessageType {

/**
* State of the WebSocket connection.
* https://developer.mozilla.org/de/docs/Web/API/WebSocket/readyState
*/
export enum WsReadyStates {
Connecting = 0,
Open = 1,
Closing = 2,

@@ -28,2 +35,7 @@ Closed = 3,

export interface ConnectionConfig {
readOnly: boolean
isAuthenticated: boolean
}
export interface Extension {

@@ -43,4 +55,13 @@ onAuthenticate?(data: onAuthenticatePayload): Promise<any>,

export interface Configuration extends Extension {
/**
* A list of hocuspocus extenions.
*/
extensions: Array<Extension>,
/**
* The port which the server listens on.
*/
port: number | null,
/**
* Defines in which interval the server sends a ping, and closes the connection when no pong is sent back.
*/
timeout: number,

@@ -51,2 +72,3 @@ }

documentName: string,
instance: Hocuspocus,
requestHeaders: IncomingHttpHeaders,

@@ -56,5 +78,3 @@ requestParameters: URLSearchParams,

token: string,
connection: {
readOnly: boolean,
},
connection: ConnectionConfig
}

@@ -64,2 +84,3 @@

documentName: string,
instance: Hocuspocus,
request: IncomingMessage,

@@ -69,5 +90,3 @@ requestHeaders: IncomingHttpHeaders,

socketId: string,
connection: {
readOnly: boolean,
},
connection: ConnectionConfig
}

@@ -82,2 +101,3 @@

socketId: string,
connection: ConnectionConfig
}

@@ -109,2 +129,3 @@

response: ServerResponse,
instance: Hocuspocus,
}

@@ -116,2 +137,3 @@

socket: Socket,
instance: Hocuspocus,
}

@@ -130,2 +152,3 @@

yjsVersion: string,
instance: Hocuspocus,
}

@@ -132,0 +155,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

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc