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.91 to 1.0.0-alpha.92

dist/tests/extension-logger/onListen.d.ts

14

dist/packages/extension-database/src/Database.d.ts

@@ -1,3 +0,2 @@

/// <reference types="node" />
import { Extension, onChangePayload, onLoadDocumentPayload } from '@hocuspocus/server';
import { Extension, onChangePayload, onLoadDocumentPayload, storePayload } from '@hocuspocus/server';
export interface DatabaseConfiguration {

@@ -8,12 +7,9 @@ /**

*/
fetchUpdates: ({ documentName }: {
fetch: ({ documentName }: {
documentName: string;
}) => Promise<Uint8Array[]>;
}) => Promise<Uint8Array | null>;
/**
* Pass a function to store updates in your database.
*/
storeUpdate: ({ update, documentName }: {
update: Buffer;
documentName: string;
}) => void;
store: (data: storePayload) => void;
}

@@ -36,3 +32,3 @@ export declare class Database implements Extension {

*/
onChange({ document, documentName }: onChangePayload): Promise<void>;
onStoreDocument(data: onChangePayload): Promise<void>;
}

@@ -18,2 +18,6 @@ import { Extension, onChangePayload, onConfigurePayload, onConnectPayload, onLoadDocumentPayload, onDestroyPayload, onDisconnectPayload, onRequestPayload, onUpgradePayload } from '@hocuspocus/server';

/**
* Whether to log something for the `onStoreDocument` hook.
*/
onStoreDocument: boolean;
/**
* Whether to log something for the `onConnect` hook.

@@ -57,2 +61,3 @@ */

onChange(data: onChangePayload): Promise<void>;
onStoreDocument(data: onDisconnectPayload): Promise<void>;
onConnect(data: onConnectPayload): Promise<void>;

@@ -59,0 +64,0 @@ onDisconnect(data: onDisconnectPayload): Promise<void>;

import { Database, DatabaseConfiguration } from '@hocuspocus/extension-database';
import sqlite3 from 'sqlite3';
export declare const schema = "CREATE TABLE IF NOT EXISTS \"documents\" (\n \"name\" varchar(255) NOT NULL,\n \"data\" blob NOT NULL,\n UNIQUE(name)\n)";
export declare const selectQuery = "\n SELECT data FROM \"documents\" WHERE name = $name ORDER BY rowid DESC\n";
export declare const upsertQuery = "\n INSERT INTO \"documents\" (\"name\", \"data\") VALUES ($name, $data)\n ON CONFLICT(name) DO UPDATE SET data = $data\n";
export interface SQLiteConfiguration extends DatabaseConfiguration {

@@ -20,8 +23,5 @@ /**

configuration: SQLiteConfiguration;
/**
* Constructor
*/
constructor(configuration?: Partial<SQLiteConfiguration>);
onConfigure(): Promise<void>;
onListen(): Promise<void>;
onConfigure(): Promise<void>;
}

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

import { AxiosResponse } from 'axios';
import Timeout = NodeJS.Timeout;
export declare enum Events {

@@ -28,3 +27,3 @@ onChange = "change",

debounced: Map<string, {
timeout: Timeout;
timeout: NodeJS.Timeout;
start: number;

@@ -31,0 +30,0 @@ }>;

@@ -8,3 +8,3 @@ export default class EventEmitter {

off(event: string, fn?: Function): this;
protected removeAllListeners(): void;
removeAllListeners(): void;
}
import * as Y from 'yjs';
import { Awareness } from 'y-protocols/awareness';
import * as mutex from 'lib0/mutex';
import { Event, CloseEvent, MessageEvent } from 'ws';
import type { Event, CloseEvent, MessageEvent } from 'ws';
import EventEmitter from './EventEmitter';
import { OutgoingMessage } from './OutgoingMessage';
import { ConstructableOutgoingMessage } from './types';
import { onAwarenessChangeParameters, onAwarenessUpdateParameters } from '.';
export declare enum WebSocketStatus {

@@ -102,8 +103,10 @@ Connecting = "connecting",

onStatus: (status: any) => void;
onSynced: () => void;
onSynced: ({ state }: {
state: boolean;
}) => void;
onDisconnect: (event: CloseEvent) => void;
onClose: (event: CloseEvent) => void;
onDestroy: () => void;
onAwarenessUpdate: (states: any) => void;
onAwarenessChange: (states: any) => void;
onAwarenessUpdate: ({ states }: onAwarenessUpdateParameters) => void;
onAwarenessChange: ({ states }: onAwarenessChangeParameters) => void;
/**

@@ -110,0 +113,0 @@ * Don’t output any warnings.

@@ -35,1 +35,11 @@ import { Awareness } from 'y-protocols/awareness';

export declare type ConstructableOutgoingMessage = Constructable<AuthenticationMessage> | Constructable<AwarenessMessage> | Constructable<QueryAwarenessMessage> | Constructable<SyncStepOneMessage> | Constructable<SyncStepTwoMessage> | Constructable<UpdateMessage>;
export declare type onAwarenessUpdateParameters = {
states: StatesArray;
};
export declare type onAwarenessChangeParameters = {
states: StatesArray;
};
export declare type StatesArray = {
clientId: number;
[key: string | number]: any;
}[];

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

import Document from './Document';
import { MessageLogger } from './Debugger';
declare class Connection {
import { Debugger } from './Debugger';
export declare class Connection {
webSocket: WebSocket;

@@ -21,7 +21,7 @@ context: any;

readOnly: Boolean;
debugger: MessageLogger;
logger: Debugger;
/**
* Constructor.
*/
constructor(connection: WebSocket, request: HTTPIncomingMessage, document: Document, timeout: number, socketId: string, context: any, readOnly?: boolean);
constructor(connection: WebSocket, request: HTTPIncomingMessage, document: Document, timeout: number, socketId: string, context: any, readOnly: boolean | undefined, logger: Debugger);
/**

@@ -28,0 +28,0 @@ * Set a callback that will be triggered when the connection is closed

@@ -1,2 +0,2 @@

export declare class MessageLogger {
export declare class Debugger {
logs: any[];

@@ -15,2 +15,1 @@ listen: boolean;

}
export declare const Debugger: MessageLogger;

@@ -6,4 +6,4 @@ import WebSocket from 'ws';

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

@@ -13,10 +13,13 @@ callbacks: {

};
connections: Map<any, any>;
connections: Map<WebSocket, {
clients: Set<any>;
connection: Connection;
}>;
name: string;
mux: mutex;
debugger: MessageLogger;
logger: Debugger;
/**
* Constructor.
*/
constructor(name: string);
constructor(name: string, logger: Debugger);
/**

@@ -23,0 +26,0 @@ * Check if the Document is empty

/// <reference types="node" />
import WebSocket, { WebSocketServer } from 'ws';
import WebSocket, { AddressInfo, WebSocketServer } from 'ws';
import { IncomingMessage, Server as HTTPServer } from 'http';
import { Configuration, Hook } from './types';
import Document from './Document';
import { MessageLogger } from './Debugger';
import { Debugger } from './Debugger';
import { onListenPayload } from '.';

@@ -12,2 +12,4 @@ export declare const defaultConfiguration: {

timeout: number;
debounce: number;
maxDebounce: number;
quiet: boolean;

@@ -23,3 +25,4 @@ };

webSocketServer?: WebSocketServer;
debugger: MessageLogger;
debugger: Debugger;
constructor(configuration?: Partial<Configuration>);
/**

@@ -33,3 +36,7 @@ * Configure the server

*/
listen(portOrCallback?: number | ((data: onListenPayload) => Promise<any>) | null, callback?: any): Promise<void>;
listen(portOrCallback?: number | ((data: onListenPayload) => Promise<any>) | null, callback?: any): Promise<Hocuspocus>;
get address(): AddressInfo;
get URL(): string;
get webSocketURL(): string;
get httpURL(): string;
private showStartScreen;

@@ -65,8 +72,14 @@ /**

* Handle update of the given document
* @private
*/
private handleDocumentUpdate;
timers: Map<string, {
timeout: NodeJS.Timeout;
start: number;
}>;
/**
* debounce the given function, using the given identifier
*/
debounce(id: string, func: Function, immediately?: boolean): void;
/**
* Create a new document by the given request
* @private
*/

@@ -76,8 +89,7 @@ private createDocument;

* Create a new connection by the given request and document
* @private
*/
private createConnection;
/**
* Run the given hook on all configured extensions
* Runs the given callback after each hook
* Run the given hook on all configured extensions.
* Runs the given callback after each hook.
*/

@@ -87,3 +99,2 @@ hooks(name: Hook, payload: any, callback?: Function | null): Promise<any>;

* Get parameters by the given request
* @private
*/

@@ -93,3 +104,2 @@ private static getParameters;

* Get document name by the given request
* @private
*/

@@ -96,0 +106,0 @@ private getDocumentNameFromRequest;

export * from './Hocuspocus';
export * from './Connection';
export * from './Document';
export * from './IncomingMessage';
export * from './OutgoingMessage';
export * from './types';
export * from './MessageReceiver';
export * from './Document';
export * from './Connection';

@@ -0,10 +1,13 @@

import { Awareness } from 'y-protocols/awareness';
import Connection from './Connection';
import { IncomingMessage } from './IncomingMessage';
import { MessageLogger } from './Debugger';
import { Debugger } from './Debugger';
import Document from './Document';
export declare class MessageReceiver {
message: IncomingMessage;
debugger: MessageLogger;
constructor(message: IncomingMessage);
apply(connection: Connection): void;
readSyncMessage(message: IncomingMessage, connection: Connection): 0 | 1 | 2;
logger: Debugger;
constructor(message: IncomingMessage, logger: Debugger);
apply(document: Document, connection?: Connection, reply?: (message: Uint8Array) => void): void;
readSyncMessage(message: IncomingMessage, document: Document, connection?: Connection, reply?: (message: Uint8Array) => void): 0 | 1 | 2;
applyQueryAwarenessMessage(awareness: Awareness, reply?: (message: Uint8Array) => void): void;
}

@@ -11,2 +11,3 @@ import { Encoder } from 'lib0/encoding';

createAwarenessUpdateMessage(awareness: Awareness, changedClients?: Array<any>): OutgoingMessage;
writeQueryAwareness(): OutgoingMessage;
writeAuthenticated(): OutgoingMessage;

@@ -13,0 +14,0 @@ writePermissionDenied(reason: string): OutgoingMessage;

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

import { Socket } from 'net';
import { Awareness } from 'y-protocols/awareness';
import Document from './Document';

@@ -12,3 +13,4 @@ import { Hocuspocus } from './Hocuspocus';

Awareness = 1,
Auth = 2
Auth = 2,
QueryAwareness = 3
}

@@ -36,6 +38,8 @@ /**

export interface Extension {
priority?: number;
onConfigure?(data: onConfigurePayload): Promise<any>;
onListen?(data: onListenPayload): Promise<any>;
onUpgrade?(data: onUpgradePayload): Promise<any>;
onConnect?(data: onConnectPayload): Promise<any>;
onAuthenticate?(data: onAuthenticatePayload): Promise<any>;
onChange?(data: onChangePayload): Promise<any>;
onConnect?(data: onConnectPayload): Promise<any>;
onConfigure?(data: onConfigurePayload): Promise<any>;
/**

@@ -46,13 +50,16 @@ * @deprecated onCreateDocument is deprecated, use onLoadDocument instead

onLoadDocument?(data: onLoadDocumentPayload): Promise<any>;
afterLoadDocument?(data: onLoadDocumentPayload): Promise<any>;
onChange?(data: onChangePayload): Promise<any>;
onStoreDocument?(data: onStoreDocumentPayload): Promise<any>;
afterStoreDocument?(data: afterStoreDocumentPayload): Promise<any>;
onAwarenessUpdate?(data: onAwarenessUpdatePayload): Promise<any>;
onRequest?(data: onRequestPayload): Promise<any>;
onDisconnect?(data: onDisconnectPayload): Promise<any>;
onDestroy?(data: onDestroyPayload): Promise<any>;
onDisconnect?(data: onDisconnectPayload): Promise<any>;
onListen?(data: onListenPayload): Promise<any>;
onRequest?(data: onRequestPayload): Promise<any>;
onUpgrade?(data: onUpgradePayload): Promise<any>;
}
export declare type Hook = 'onAuthenticate' | 'onChange' | 'onConnect' | 'onConfigure' |
export declare type Hook = 'onConfigure' | 'onListen' | 'onUpgrade' | 'onConnect' | 'onAuthenticate' |
/**
* @deprecated onCreateDocument is deprecated, use onLoadDocument instead
*/
'onCreateDocument' | 'onLoadDocument' | 'onDestroy' | 'onDisconnect' | 'onListen' | 'onRequest' | 'onUpgrade';
'onCreateDocument' | 'onLoadDocument' | 'afterLoadDocument' | 'onChange' | 'onStoreDocument' | 'afterStoreDocument' | 'onAwarenessUpdate' | 'onRequest' | 'onDisconnect' | 'onDestroy';
export interface Configuration extends Extension {

@@ -76,2 +83,11 @@ /**

/**
* Debounces the call of the `onStoreDocument` hook for the given amount of time in ms.
* Otherwise every single update would be persisted.
*/
debounce: number;
/**
* Makes sure to call `onStoreDocument` at least in the given amount of time (ms).
*/
maxDebounce: number;
/**
* By default, the servers show a start screen. If passed false, the server will start quietly.

@@ -83,8 +99,9 @@ */

*/
getDocumentName?(data: {
documentName: string;
request: IncomingMessage;
requestParameters: URLSearchParams;
}): string | Promise<string>;
getDocumentName?(data: getDocumentNamePayload): string | Promise<string>;
}
export interface getDocumentNamePayload {
documentName: string;
request: IncomingMessage;
requestParameters: URLSearchParams;
}
export interface onAuthenticatePayload {

@@ -118,2 +135,12 @@ documentName: string;

}
export interface afterLoadDocumentPayload {
context: any;
document: Document;
documentName: string;
instance: Hocuspocus;
requestHeaders: IncomingHttpHeaders;
requestParameters: URLSearchParams;
socketId: string;
connection: ConnectionConfiguration;
}
export interface onChangePayload {

@@ -130,2 +157,37 @@ clientsCount: number;

}
export interface onStoreDocumentPayload {
clientsCount: number;
context: any;
document: Document;
documentName: string;
instance: Hocuspocus;
requestHeaders: IncomingHttpHeaders;
requestParameters: URLSearchParams;
socketId: string;
}
export interface afterStoreDocumentPayload extends onStoreDocumentPayload {
}
export interface onAwarenessUpdatePayload {
clientsCount: number;
context: any;
document: Document;
documentName: string;
instance: Hocuspocus;
requestHeaders: IncomingHttpHeaders;
requestParameters: URLSearchParams;
update: Uint8Array;
socketId: string;
added: number[];
updated: number[];
removed: number[];
awareness: Awareness;
states: StatesArray;
}
export declare type StatesArray = {
clientId: number;
[key: string | number]: any;
}[];
export interface storePayload extends onStoreDocumentPayload {
state: Buffer;
}
export interface onDisconnectPayload {

@@ -132,0 +194,0 @@ clientsCount: number;

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

@@ -31,6 +31,6 @@ "keywords": [

"dependencies": {
"@hocuspocus/common": "^1.0.0-alpha.4",
"@hocuspocus/common": "^1.0.0-alpha.5",
"@types/async-lock": "^1.1.3",
"@types/uuid": "^8.3.3",
"@types/ws": "^8.2.1",
"@types/uuid": "^8.3.4",
"@types/ws": "^8.2.2",
"async-lock": "^1.3.0",

@@ -40,7 +40,7 @@ "kleur": "^4.1.4",

"uuid": "^8.3.2",
"ws": "^8.3.0",
"ws": "^8.4.2",
"y-protocols": "^1.0.5",
"yjs": "^13.5.22"
"yjs": "^13.5.24"
},
"gitHead": "90e3f0955922515ccf1d643b7b43c4ad20387735"
"gitHead": "125d94dc3520bb5185293c9b316f66ddd9215992"
}

@@ -10,5 +10,5 @@ import AsyncLock from 'async-lock'

import { MessageReceiver } from './MessageReceiver'
import { Debugger, MessageLogger } from './Debugger'
import { Debugger } from './Debugger'
class Connection {
export class Connection {

@@ -39,3 +39,3 @@ webSocket: WebSocket

debugger: MessageLogger = Debugger
logger: Debugger

@@ -53,2 +53,3 @@ /**

readOnly = false,
logger: Debugger,
) {

@@ -62,2 +63,3 @@ this.webSocket = connection

this.readOnly = readOnly
this.logger = logger

@@ -161,3 +163,3 @@ this.lock = new AsyncLock()

this.debugger.log({
this.logger.log({
direction: 'out',

@@ -178,3 +180,4 @@ type: awarenessMessage.type,

new IncomingMessage(data),
).apply(this)
this.logger,
).apply(this.document, this)
}

@@ -181,0 +184,0 @@

// import * as time from 'lib0/time'
import { MessageType } from './types'
export class MessageLogger {
export class Debugger {
logs: any[] = []

@@ -61,3 +61,1 @@

}
export const Debugger = new MessageLogger()

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

import { OutgoingMessage } from './OutgoingMessage'
import { Debugger, MessageLogger } from './Debugger'
import { Debugger } from './Debugger'
class Document extends Doc {
export class Document extends Doc {

@@ -20,3 +20,6 @@ awareness: Awareness

connections = new Map()
connections: Map<WebSocket, {
clients: Set<any>,
connection: Connection
}> = new Map()

@@ -27,3 +30,3 @@ name: string

debugger: MessageLogger = Debugger
logger: Debugger

@@ -33,3 +36,3 @@ /**

*/
constructor(name: string) {
constructor(name: string, logger: Debugger) {
super({ gc: true })

@@ -45,2 +48,4 @@

this.on('update', this.handleUpdate.bind(this))
this.logger = logger
}

@@ -131,3 +136,3 @@

return connection.clients === undefined ? new Set() : connection.clients
return connection?.clients === undefined ? new Set() : connection.clients
}

@@ -178,3 +183,3 @@

this.debugger.log({
this.logger.log({
direction: 'out',

@@ -204,3 +209,3 @@ type: awarenessMessage.type,

this.getConnections().forEach(connection => {
this.debugger.log({
this.logger.log({
direction: 'out',

@@ -207,0 +212,0 @@ type: message.type,

import * as decoding from 'lib0/decoding'
import WebSocket, { WebSocketServer } from 'ws'
import WebSocket, { AddressInfo, WebSocketServer } from 'ws'
import { createServer, IncomingMessage, Server as HTTPServer } from 'http'

@@ -9,2 +9,3 @@ import { Doc, encodeStateAsUpdate, applyUpdate } from 'yjs'

import { ResetConnection, Unauthorized, Forbidden } from '@hocuspocus/common'
import { awarenessStatesToArray } from '@hocuspocus/provider'
import {

@@ -16,2 +17,3 @@ MessageType,

Hook,
AwarenessUpdate,
} from './types'

@@ -22,3 +24,3 @@ import Document from './Document'

import meta from '../package.json'
import { Debugger, MessageLogger } from './Debugger'
import { Debugger } from './Debugger'
import { onListenPayload } from '.'

@@ -30,2 +32,4 @@

timeout: 30000,
debounce: 2000,
maxDebounce: 10000,
quiet: false,

@@ -43,12 +47,15 @@ }

extensions: [],
onChange: () => new Promise(r => r(null)),
onConfigure: () => new Promise(r => r(null)),
onListen: () => new Promise(r => r(null)),
onUpgrade: () => new Promise(r => r(null)),
onConnect: () => new Promise(r => r(null)),
onChange: () => new Promise(r => r(null)),
onCreateDocument: defaultOnCreateDocument,
onLoadDocument: () => new Promise(r => r(null)),
onStoreDocument: () => new Promise(r => r(null)),
afterStoreDocument: () => new Promise(r => r(null)),
onAwarenessUpdate: () => new Promise(r => r(null)),
onRequest: () => new Promise(r => r(null)),
onDisconnect: () => new Promise(r => r(null)),
onDestroy: () => new Promise(r => r(null)),
onDisconnect: () => new Promise(r => r(null)),
onListen: () => new Promise(r => r(null)),
onRequest: () => new Promise(r => r(null)),
onUpgrade: () => new Promise(r => r(null)),
}

@@ -62,4 +69,10 @@

debugger: MessageLogger = Debugger
debugger = new Debugger()
constructor(configuration?: Partial<Configuration>) {
if (configuration) {
this.configure(configuration)
}
}
/**

@@ -86,13 +99,31 @@ * Configure the server

this.configuration.extensions.sort((a, b) => {
const one = typeof a.priority === 'undefined' ? 100 : a.priority
const two = typeof b.priority === 'undefined' ? 100 : b.priority
if (one > two) {
return -1
}
if (one < two) {
return 1
}
return 0
})
this.configuration.extensions.push({
onAuthenticate: this.configuration.onAuthenticate,
onChange: this.configuration.onChange,
onConfigure: this.configuration.onConfigure,
onListen: this.configuration.onListen,
onUpgrade: this.configuration.onUpgrade,
onConnect: this.configuration.onConnect,
onAuthenticate: this.configuration.onAuthenticate,
onLoadDocument,
onChange: this.configuration.onChange,
onStoreDocument: this.configuration.onStoreDocument,
afterStoreDocument: this.configuration.afterStoreDocument,
onAwarenessUpdate: this.configuration.onAwarenessUpdate,
onRequest: this.configuration.onRequest,
onDisconnect: this.configuration.onDisconnect,
onDestroy: this.configuration.onDestroy,
onDisconnect: this.configuration.onDisconnect,
onListen: this.configuration.onListen,
onRequest: this.configuration.onRequest,
onUpgrade: this.configuration.onUpgrade,
})

@@ -122,3 +153,3 @@

callback: any = null,
): Promise<void> {
): Promise<Hocuspocus> {
if (typeof portOrCallback === 'number') {

@@ -153,3 +184,3 @@ this.configuration.port = portOrCallback

})
.catch(e => {
.catch(error => {
// if a hook rejects and the error is empty, do nothing

@@ -159,3 +190,5 @@ // this is only meant to prevent later hooks and the

// just rethrow it
if (e) throw e
if (error) {
throw error
}
})

@@ -177,3 +210,3 @@ })

})
.catch(e => {
.catch(error => {
// if a hook rejects and the error is empty, do nothing

@@ -183,3 +216,5 @@ // this is only meant to prevent later hooks and the

// just rethrow it
if (e) throw e
if (error) {
throw error
}
})

@@ -191,3 +226,3 @@ })

await new Promise((resolve: Function, reject: Function) => {
return new Promise((resolve: Function, reject: Function) => {
server.listen(this.configuration.port, () => {

@@ -198,5 +233,5 @@ if (!this.configuration.quiet && process.env.NODE_ENV !== 'testing') {

this.hooks('onListen', { port: this.configuration.port })
.then(() => resolve())
.catch(e => reject(e))
this.hooks('onListen', { port: this.address.port })
.then(() => resolve(this))
.catch(error => reject(error))
})

@@ -206,2 +241,22 @@ })

get address(): AddressInfo {
return (this.httpServer?.address() || {
port: this.configuration.port,
address: '127.0.0.1',
family: 'IPv4',
}) as AddressInfo
}
get URL(): string {
return `127.0.0.1:${this.address.port}`
}
get webSocketURL(): string {
return `ws://${this.URL}`
}
get httpURL(): string {
return `http://${this.URL}`
}
private showStartScreen() {

@@ -213,4 +268,4 @@ const name = this.configuration.name ? ` (${this.configuration.name})` : ''

console.log()
console.log(` > HTTP: ${kleur.cyan(`http://127.0.0.1:${this.configuration.port}`)}`)
console.log(` > WebSocket: ws://127.0.0.1:${this.configuration.port}`)
console.log(` > HTTP: ${kleur.cyan(`${this.httpURL}`)}`)
console.log(` > WebSocket: ${this.webSocketURL}`)

@@ -270,3 +325,3 @@ const extensions = this.configuration?.extensions.map(extension => {

document.connections.forEach(({ connection } = { connection: Connection }) => {
document.connections.forEach(({ connection }) => {
connection.close(ResetConnection)

@@ -288,3 +343,3 @@ })

})
} catch (e) {
} catch (error) {
//

@@ -449,3 +504,2 @@ }

* Handle update of the given document
* @private
*/

@@ -465,10 +519,63 @@ private handleDocumentUpdate(document: Document, connection: Connection, update: Uint8Array, request: IncomingMessage, socketId: string): void {

this.hooks('onChange', hookPayload).catch(e => {
throw e
this.hooks('onChange', hookPayload).catch(error => {
throw error
})
// If the update was received through other ways than the
// WebSocket connection, we don’t need to feel responsible for
// storing the content.
if (!connection) {
return
}
this.debounce(`onStoreDocument-${document.name}`, () => {
this.hooks('onStoreDocument', hookPayload)
.catch(error => {
if (error?.message) {
throw error
}
})
.then(() => {
this.hooks('afterStoreDocument', hookPayload)
})
})
}
timers: Map<string, {
timeout: NodeJS.Timeout,
start: number
}> = new Map()
/**
* debounce the given function, using the given identifier
*/
debounce(id: string, func: Function, immediately = false) {
const old = this.timers.get(id)
const start = old?.start || Date.now()
const run = () => {
this.timers.delete(id)
func()
}
if (old?.timeout) {
clearTimeout(old.timeout)
}
if (immediately) {
return run()
}
if (Date.now() - start >= this.configuration.maxDebounce) {
return run()
}
this.timers.set(id, {
start,
timeout: setTimeout(run, this.configuration.debounce),
})
}
/**
* Create a new document by the given request
* @private
*/

@@ -484,3 +591,3 @@ private async createDocument(documentName: string, request: IncomingMessage, socketId: string, connection: ConnectionConfiguration, context?: any): Promise<Document> {

const document = new Document(documentName)
const document = new Document(documentName, this.debugger)
this.documents.set(documentName, document)

@@ -511,2 +618,4 @@

await this.hooks('afterLoadDocument', hookPayload)
document.onUpdate((document: Document, connection: Connection, update: Uint8Array) => {

@@ -516,2 +625,11 @@ this.handleDocumentUpdate(document, connection, update, request, connection?.socketId)

document.awareness.on('update', ({ update }: { update: AwarenessUpdate }) => {
this.hooks('onAwarenessUpdate', {
...hookPayload,
...update,
awareness: document.awareness,
states: awarenessStatesToArray(document.awareness.getStates()),
})
})
return document

@@ -522,6 +640,14 @@ }

* Create a new connection by the given request and document
* @private
*/
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)
const instance = new Connection(
connection,
request,
document,
this.configuration.timeout,
socketId,
context,
readOnly,
this.debugger,
)

@@ -540,23 +666,34 @@ instance.onClose(document => {

// Remove the document from the map immediately before the hooks are called
// as these may take some time to resolve (eg persist to database). If a
this.hooks('onDisconnect', hookPayload)
// Check if there are still no connections to the document, as these hooks
// may take some time to resolve (e.g. database queries). If a
// new connection were to come in during that time it would rely on the
// document in the map that we later remove.
if (document.getConnectionsCount() <= 0) {
this.documents.delete(document.name)
// document in the map that we remove now.
if (document.getConnectionsCount() > 0) {
return
}
this.hooks('onDisconnect', hookPayload)
.catch(e => {
throw e
})
.finally(() => {
if (document.getConnectionsCount() <= 0) {
document.destroy()
}
})
// If it’s the last connection, we need to make sure to store the
// document. Use the debounce helper, to clear running timers,
// but make it run immediately (`true`).
this.debounce(`onStoreDocument-${document.name}`, () => {
this.hooks('onStoreDocument', hookPayload)
.catch(error => {
if (error?.message) {
throw error
}
})
.then(() => {
this.hooks('afterStoreDocument', hookPayload)
})
}, true)
// Remove document from memory.
this.documents.delete(document.name)
document.destroy()
})
// If the websocket has already disconnected (wow, that was fast) – then
// immediately call close to cleanup the connection and doc in memory.
// If the WebSocket has already disconnected (wow, that was fast) – then
// immediately call close to cleanup the connection and document in memory.
if (

@@ -573,4 +710,4 @@ connection.readyState === WsReadyStates.Closing

/**
* Run the given hook on all configured extensions
* Runs the given callback after each hook
* Run the given hook on all configured extensions.
* Runs the given callback after each hook.
*/

@@ -593,3 +730,3 @@ hooks(name: Hook, payload: any, callback: Function | null = null): Promise<any> {

// make sure to log error messages
if (error && error.message) {
if (error?.message) {
console.error(`[${name}]`, error.message)

@@ -611,3 +748,2 @@ }

* Get parameters by the given request
* @private
*/

@@ -621,3 +757,2 @@ private static getParameters(request: IncomingMessage): URLSearchParams {

* Get document name by the given request
* @private
*/

@@ -624,0 +759,0 @@ private async getDocumentNameFromRequest(request: IncomingMessage): Promise<string> {

export * from './Hocuspocus'
export * from './Connection'
export * from './Document'
export * from './IncomingMessage'
export * from './OutgoingMessage'
export * from './types'
export * from './MessageReceiver'
export * from './Document'
export * from './Connection'

@@ -9,3 +9,3 @@ import {

} from 'y-protocols/sync'
import { applyAwarenessUpdate } from 'y-protocols/awareness'
import { applyAwarenessUpdate, Awareness } from 'y-protocols/awareness'
import { MessageType } from './types'

@@ -15,3 +15,4 @@ import Connection from './Connection'

import { OutgoingMessage } from './OutgoingMessage'
import { Debugger, MessageLogger } from './Debugger'
import { Debugger } from './Debugger'
import Document from './Document'

@@ -22,10 +23,10 @@ export class MessageReceiver {

debugger: MessageLogger = Debugger
logger: Debugger
constructor(message: IncomingMessage) {
constructor(message: IncomingMessage, logger: Debugger) {
this.message = message
this.logger = logger
}
public apply(connection: Connection) {
const { document } = connection
public apply(document: Document, connection?: Connection, reply?: (message: Uint8Array) => void) {
const { message } = this

@@ -37,6 +38,16 @@ const type = message.readVarUint()

message.writeVarUint(MessageType.Sync)
this.readSyncMessage(message, connection)
this.readSyncMessage(message, document, connection, reply)
if (message.length > 1) {
connection.send(message.toUint8Array())
if (reply) {
reply(message.toUint8Array())
} else if (connection) {
// TODO: We should log this, shouldn’t we?
// this.logger.log({
// direction: 'out',
// type: MessageType.Awareness,
// category: 'Update',
// })
connection.send(message.toUint8Array())
}
}

@@ -46,3 +57,3 @@

case MessageType.Awareness:
this.debugger.log({
this.logger.log({
direction: 'in',

@@ -56,2 +67,7 @@ type: MessageType.Awareness,

break
case MessageType.QueryAwareness:
this.applyQueryAwarenessMessage(document.awareness, reply)
break
default:

@@ -62,4 +78,3 @@ // Do nothing

readSyncMessage(message: IncomingMessage, connection: Connection) {
const { document } = connection
readSyncMessage(message: IncomingMessage, document: Document, connection?: Connection, reply?: (message: Uint8Array) => void) {
const type = message.readVarUint()

@@ -69,3 +84,3 @@

case messageYjsSyncStep1: {
this.debugger.log({
this.logger.log({
direction: 'in',

@@ -79,3 +94,3 @@ type: MessageType.Sync,

// When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1.
this.debugger.log({
this.logger.log({
direction: 'out',

@@ -90,9 +105,13 @@ type: MessageType.Sync,

this.debugger.log({
direction: 'out',
type: MessageType.Sync,
category: 'SyncStep1',
})
if (reply) {
reply(syncMessage.toUint8Array())
} else if (connection) {
this.logger.log({
direction: 'out',
type: MessageType.Sync,
category: 'SyncStep1',
})
connection.send(syncMessage.toUint8Array())
connection.send(syncMessage.toUint8Array())
}

@@ -102,3 +121,3 @@ break

case messageYjsSyncStep2:
this.debugger.log({
this.logger.log({
direction: 'in',

@@ -116,3 +135,3 @@ type: MessageType.Sync,

case messageYjsUpdate:
this.debugger.log({
this.logger.log({
direction: 'in',

@@ -135,2 +154,20 @@ type: MessageType.Sync,

}
applyQueryAwarenessMessage(awareness: Awareness, reply?: (message: Uint8Array) => void) {
const message = new OutgoingMessage()
.createAwarenessUpdateMessage(awareness)
if (reply) {
reply(message.toUint8Array())
}
// TODO: We should add support for WebSocket connections, too, right?
// this.logger.log({
// direction: 'out',
// type: MessageType.Sync,
// category: 'SyncStep1',
// })
// connection.send(syncMessage.toUint8Array())
}
}

@@ -50,2 +50,11 @@ import {

writeQueryAwareness(): OutgoingMessage {
this.type = MessageType.QueryAwareness
this.category = 'Update'
writeVarUint(this.encoder, MessageType.QueryAwareness)
return this
}
writeAuthenticated(): OutgoingMessage {

@@ -52,0 +61,0 @@ this.type = MessageType.Auth

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

import { Socket } from 'net'
import { Awareness } from 'y-protocols/awareness'
import Document from './Document'

@@ -15,2 +16,3 @@ import { Hocuspocus } from './Hocuspocus'

Auth = 2,
QueryAwareness = 3,
}

@@ -42,6 +44,8 @@

export interface Extension {
priority?: number,
onConfigure?(data: onConfigurePayload): Promise<any>,
onListen?(data: onListenPayload): Promise<any>,
onUpgrade?(data: onUpgradePayload): Promise<any>,
onConnect?(data: onConnectPayload): Promise<any>,
onAuthenticate?(data: onAuthenticatePayload): Promise<any>,
onChange?(data: onChangePayload): Promise<any>,
onConnect?(data: onConnectPayload): Promise<any>,
onConfigure?(data: onConfigurePayload): Promise<any>,
/**

@@ -52,14 +56,18 @@ * @deprecated onCreateDocument is deprecated, use onLoadDocument instead

onLoadDocument?(data: onLoadDocumentPayload): Promise<any>,
afterLoadDocument?(data: onLoadDocumentPayload): Promise<any>,
onChange?(data: onChangePayload): Promise<any>,
onStoreDocument?(data: onStoreDocumentPayload): Promise<any>,
afterStoreDocument?(data: afterStoreDocumentPayload): Promise<any>,
onAwarenessUpdate?(data: onAwarenessUpdatePayload): Promise<any>,
onRequest?(data: onRequestPayload): Promise<any>,
onDisconnect?(data: onDisconnectPayload): Promise<any>
onDestroy?(data: onDestroyPayload): Promise<any>,
onDisconnect?(data: onDisconnectPayload): Promise<any>
onListen?(data: onListenPayload): Promise<any>,
onRequest?(data: onRequestPayload): Promise<any>,
onUpgrade?(data: onUpgradePayload): Promise<any>,
}
export type Hook =
'onConfigure' |
'onListen' |
'onUpgrade' |
'onConnect' |
'onAuthenticate' |
'onChange' |
'onConnect' |
'onConfigure' |
/**

@@ -70,7 +78,10 @@ * @deprecated onCreateDocument is deprecated, use onLoadDocument instead

'onLoadDocument' |
'onDestroy' |
'afterLoadDocument' |
'onChange' |
'onStoreDocument' |
'afterStoreDocument' |
'onAwarenessUpdate' |
'onRequest' |
'onDisconnect' |
'onListen' |
'onRequest' |
'onUpgrade'
'onDestroy'

@@ -95,2 +106,11 @@ export interface Configuration extends Extension {

/**
* Debounces the call of the `onStoreDocument` hook for the given amount of time in ms.
* Otherwise every single update would be persisted.
*/
debounce: number,
/**
* Makes sure to call `onStoreDocument` at least in the given amount of time (ms).
*/
maxDebounce: number
/**
* By default, the servers show a start screen. If passed false, the server will start quietly.

@@ -102,9 +122,11 @@ */

*/
getDocumentName?(data: {
documentName: string,
request: IncomingMessage,
requestParameters: URLSearchParams,
}): string | Promise<string>,
getDocumentName?(data: getDocumentNamePayload): string | Promise<string>,
}
export interface getDocumentNamePayload {
documentName: string,
request: IncomingMessage,
requestParameters: URLSearchParams,
}
export interface onAuthenticatePayload {

@@ -141,2 +163,13 @@ documentName: string,

export interface afterLoadDocumentPayload {
context: any,
document: Document,
documentName: string,
instance: Hocuspocus,
requestHeaders: IncomingHttpHeaders,
requestParameters: URLSearchParams,
socketId: string,
connection: ConnectionConfiguration
}
export interface onChangePayload {

@@ -154,2 +187,38 @@ clientsCount: number,

export interface onStoreDocumentPayload {
clientsCount: number,
context: any,
document: Document,
documentName: string,
instance: Hocuspocus,
requestHeaders: IncomingHttpHeaders,
requestParameters: URLSearchParams,
socketId: string,
}
export interface afterStoreDocumentPayload extends onStoreDocumentPayload {}
export interface onAwarenessUpdatePayload {
clientsCount: number,
context: any,
document: Document,
documentName: string,
instance: Hocuspocus,
requestHeaders: IncomingHttpHeaders,
requestParameters: URLSearchParams,
update: Uint8Array,
socketId: string,
added: number[],
updated: number[],
removed: number[],
awareness: Awareness,
states: StatesArray,
}
export type StatesArray = { clientId: number, [key: string | number]: any }[]
export interface storePayload extends onStoreDocumentPayload {
state: Buffer,
}
export interface onDisconnectPayload {

@@ -156,0 +225,0 @@ clientsCount: number,

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 too big to display

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