@hocuspocus/extension-redis 1.1.1 → 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,6 +37,10 @@ class Redis {
37
37
  * in Redis to filter these.
38
38
  */
39
39
  this.handleIncomingMessage = async (channel, pattern, data) => {
40
+ const message = new server.IncomingMessage(data);
41
+ // we don't need the documentName from the message, we are just taking it from the redis channelName.
42
+ // we have to immediately write it back to the encoder though, to make sure the structure of the message is correct
43
+ message.writeVarString(message.readVarString());
40
44
  const channelName = pattern.toString();
41
45
  const [_, documentName, identifier] = channelName.split(':');
42
46
  const document = this.documents.get(documentName);
@@ -46,7 +50,7 @@ class Redis {
46
50
  if (!document) {
47
51
  return;
48
52
  }
49
- new server.MessageReceiver(new server.IncomingMessage(data), this.logger).apply(document, undefined, reply => {
53
+ new server.MessageReceiver(message, this.logger).apply(document, undefined, reply => {
50
54
  return this.pub.publishBuffer(this.pubKey(document.name), Buffer.from(reply));
51
55
  });
52
56
  };
@@ -140,7 +144,7 @@ class Redis {
140
144
  * Publish the first sync step through Redis.
141
145
  */
142
146
  async publishFirstSyncStep(documentName, document) {
143
- const syncMessage = new server.OutgoingMessage()
147
+ const syncMessage = new server.OutgoingMessage(documentName)
144
148
  .createSyncMessage()
145
149
  .writeFirstSyncStepFor(document);
146
150
  return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(syncMessage.toUint8Array()));
@@ -149,7 +153,7 @@ class Redis {
149
153
  * Let’s ask Redis who is connected already.
150
154
  */
151
155
  async requestAwarenessFromOtherInstances(documentName) {
152
- const awarenessMessage = new server.OutgoingMessage()
156
+ const awarenessMessage = new server.OutgoingMessage(documentName)
153
157
  .writeQueryAwareness();
154
158
  return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(awarenessMessage.toUint8Array()));
155
159
  }
@@ -190,7 +194,7 @@ class Redis {
190
194
  */
191
195
  async onAwarenessUpdate({ documentName, awareness, added, updated, removed, }) {
192
196
  const changedClients = added.concat(updated, removed);
193
- const message = new server.OutgoingMessage()
197
+ const message = new server.OutgoingMessage(documentName)
194
198
  .createAwarenessUpdateMessage(awareness, changedClients);
195
199
  return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(message.toUint8Array()));
196
200
  }
@@ -201,7 +205,7 @@ class Redis {
201
205
  return this.publishFirstSyncStep(data.documentName, data.document);
202
206
  }
203
207
  async beforeBroadcastStateless(data) {
204
- const message = new server.OutgoingMessage()
208
+ const message = new server.OutgoingMessage(data.documentName)
205
209
  .writeBroadcastStateless(data.payload);
206
210
  return this.pub.publishBuffer(this.pubKey(data.documentName), Buffer.from(message.toUint8Array()));
207
211
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hocuspocus-redis.cjs","sources":["../src/Redis.ts"],"sourcesContent":["import RedisClient, { ClusterNode, ClusterOptions, RedisOptions } from 'ioredis'\nimport Redlock from 'redlock'\nimport { v4 as uuid } from 'uuid'\nimport {\n IncomingMessage,\n OutgoingMessage,\n Document,\n Extension,\n afterLoadDocumentPayload,\n afterStoreDocumentPayload,\n onDisconnectPayload,\n onStoreDocumentPayload,\n onAwarenessUpdatePayload,\n onChangePayload,\n MessageReceiver,\n Debugger,\n onConfigurePayload,\n onListenPayload,\n beforeBroadcastStatelessPayload,\n} from '@hocuspocus/server'\nimport kleur from 'kleur'\n\nexport type RedisInstance = RedisClient.Cluster | RedisClient.Redis\n\nexport interface Configuration {\n /**\n * Redis port\n */\n port: number,\n /**\n * Redis host\n */\n host: string,\n /**\n * Redis Cluster\n */\n nodes?: ClusterNode[],\n /**\n * Duplicate from an existed Redis instance\n */\n redis?: RedisInstance,\n /**\n * Redis instance creator\n */\n createClient?: () => RedisInstance,\n /**\n * Options passed directly to Redis constructor\n *\n * https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options\n */\n options?: ClusterOptions | RedisOptions,\n /**\n * An unique instance name, required to filter messages in Redis.\n * If none is provided an unique id is generated.\n */\n identifier: string,\n /**\n * Namespace for Redis keys, if none is provided 'hocuspocus' is used\n */\n prefix: string,\n /**\n * The maximum time for the Redis lock in ms (in case it can’t be released).\n */\n lockTimeout: number,\n}\n\nexport class Redis implements Extension {\n /**\n * Make sure to give that extension a higher priority, so\n * the `onStoreDocument` hook is able to intercept the chain,\n * before documents are stored to the database.\n */\n priority = 1000\n\n configuration: Configuration = {\n port: 6379,\n host: '127.0.0.1',\n prefix: 'hocuspocus',\n identifier: `host-${uuid()}`,\n lockTimeout: 1000,\n }\n\n pub: RedisInstance\n\n sub: RedisInstance\n\n documents: Map<string, Document> = new Map()\n\n redlock: Redlock\n\n locks = new Map<string, Redlock.Lock>()\n\n logger: Debugger\n\n public constructor(configuration: Partial<Configuration>) {\n this.configuration = {\n ...this.configuration,\n ...configuration,\n }\n\n // We’ll replace that in the onConfigure hook with the global instance.\n this.logger = new Debugger()\n\n // Create Redis instance\n const {\n port,\n host,\n options,\n nodes,\n redis,\n createClient,\n } = this.configuration\n\n if (typeof createClient === 'function') {\n this.pub = createClient()\n this.sub = createClient()\n } else if (redis) {\n this.pub = redis.duplicate()\n this.sub = redis.duplicate()\n } else if (nodes && nodes.length > 0) {\n this.pub = new RedisClient.Cluster(nodes, options)\n this.sub = new RedisClient.Cluster(nodes, options)\n } else {\n this.pub = new RedisClient(port, host, options)\n this.sub = new RedisClient(port, host, options)\n }\n this.sub.on('pmessageBuffer', this.handleIncomingMessage)\n\n this.redlock = new Redlock([this.pub])\n }\n\n async onConfigure({ instance }: onConfigurePayload) {\n this.logger = instance.debugger\n }\n\n async onListen({ configuration }: onListenPayload) {\n if (configuration.quiet) {\n return\n }\n\n console.warn(` ${kleur.yellow('[BREAKING CHANGE] Wait, the Redis extension got an overhaul. The new Redis extension doesn’t persist data, it only syncs data between instances. Use @hocuspocus/extension-database to store your documents. It works well with the Redis extension.')}`)\n console.log()\n }\n\n private getKey(documentName: string) {\n return `${this.configuration.prefix}:${documentName}`\n }\n\n private pubKey(documentName: string) {\n return `${this.getKey(documentName)}:${this.configuration.identifier.replace(/:/g, '')}`\n }\n\n private subKey(documentName: string) {\n return `${this.getKey(documentName)}:*`\n }\n\n private lockKey(documentName: string) {\n return `${this.getKey(documentName)}:lock`\n }\n\n /**\n * Once a document is laoded, subscribe to the channel in Redis.\n */\n public async afterLoadDocument({ documentName, document }: afterLoadDocumentPayload) {\n this.documents.set(documentName, document)\n\n return new Promise((resolve, reject) => {\n // On document creation the node will connect to pub and sub channels\n // for the document.\n this.sub.psubscribe(this.subKey(documentName), async error => {\n if (error) {\n reject(error)\n return\n }\n\n this.publishFirstSyncStep(documentName, document)\n this.requestAwarenessFromOtherInstances(documentName)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Publish the first sync step through Redis.\n */\n private async publishFirstSyncStep(documentName: string, document: Document) {\n const syncMessage = new OutgoingMessage()\n .createSyncMessage()\n .writeFirstSyncStepFor(document)\n\n return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(syncMessage.toUint8Array()))\n }\n\n /**\n * Let’s ask Redis who is connected already.\n */\n private async requestAwarenessFromOtherInstances(documentName: string) {\n const awarenessMessage = new OutgoingMessage()\n .writeQueryAwareness()\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(awarenessMessage.toUint8Array()),\n )\n }\n\n /**\n * Before the document is stored, make sure to set a lock in Redis.\n * That’s meant to avoid conflicts with other instances trying to store the document.\n */\n async onStoreDocument({ documentName }: onStoreDocumentPayload) {\n // Attempt to acquire a lock and read lastReceivedTimestamp from Redis,\n // to avoid conflict with other instances storing the same document.\n return new Promise((resolve, reject) => {\n this.redlock.lock(this.lockKey(documentName), this.configuration.lockTimeout, async (error, lock) => {\n if (error || !lock) {\n // Expected behavior: Could not acquire lock, another instance locked it already.\n // No further `onStoreDocument` hooks will be executed.\n reject()\n return\n }\n\n this.locks.set(this.lockKey(documentName), lock)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Release the Redis lock, so other instances can store documents.\n */\n async afterStoreDocument({ documentName }: afterStoreDocumentPayload) {\n this.locks.get(this.lockKey(documentName))?.unlock()\n .catch(() => {\n // Not able to unlock Redis. The lock will expire after ${lockTimeout} ms.\n // console.error(`Not able to unlock Redis. The lock will expire after ${this.configuration.lockTimeout}ms.`)\n })\n .finally(() => {\n this.locks.delete(this.lockKey(documentName))\n })\n }\n\n /**\n * Handle awareness update messages received directly by this Hocuspocus instance.\n */\n async onAwarenessUpdate({\n documentName, awareness, added, updated, removed,\n }: onAwarenessUpdatePayload) {\n const changedClients = added.concat(updated, removed)\n const message = new OutgoingMessage()\n .createAwarenessUpdateMessage(awareness, changedClients)\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Handle incoming messages published on all subscribed document channels.\n * Note that this will also include messages from ourselves as it is not possible\n * in Redis to filter these.\n */\n private handleIncomingMessage = async (channel: Buffer, pattern: Buffer, data: Buffer) => {\n const channelName = pattern.toString()\n const [_, documentName, identifier] = channelName.split(':')\n const document = this.documents.get(documentName)\n\n if (identifier === this.configuration.identifier) {\n return\n }\n\n if (!document) {\n return\n }\n\n new MessageReceiver(\n new IncomingMessage(data),\n this.logger,\n ).apply(document, undefined, reply => {\n return this.pub.publishBuffer(\n this.pubKey(document.name),\n Buffer.from(reply),\n )\n })\n }\n\n /**\n * if the ydoc changed, we'll need to inform other Hocuspocus servers about it.\n */\n public async onChange(data: onChangePayload): Promise<any> {\n return this.publishFirstSyncStep(data.documentName, data.document)\n }\n\n /**\n * Make sure to *not* listen for further changes, when there’s\n * noone connected anymore.\n */\n public onDisconnect = async ({ documentName, clientsCount }: onDisconnectPayload) => {\n // Do nothing, when other users are still connected to the document.\n if (clientsCount > 0) {\n return\n }\n\n // It was indeed the last connected user.\n this.documents.delete(documentName)\n\n // Time to end the subscription on the document channel.\n this.sub.punsubscribe(this.subKey(documentName), error => {\n if (error) {\n console.error(error)\n }\n })\n }\n\n async beforeBroadcastStateless(data: beforeBroadcastStatelessPayload) {\n const message = new OutgoingMessage()\n .writeBroadcastStateless(data.payload)\n\n return this.pub.publishBuffer(\n this.pubKey(data.documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Kill the Redlock connection immediately.\n */\n async onDestroy() {\n this.redlock.quit()\n }\n}\n"],"names":["uuid","MessageReceiver","IncomingMessage","Debugger","RedisClient","Redlock","kleur","OutgoingMessage"],"mappings":"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"}
1
+ {"version":3,"file":"hocuspocus-redis.cjs","sources":["../src/Redis.ts"],"sourcesContent":["import RedisClient, { ClusterNode, ClusterOptions, RedisOptions } from 'ioredis'\nimport Redlock from 'redlock'\nimport { v4 as uuid } from 'uuid'\nimport {\n IncomingMessage,\n OutgoingMessage,\n Document,\n Extension,\n afterLoadDocumentPayload,\n afterStoreDocumentPayload,\n onDisconnectPayload,\n onStoreDocumentPayload,\n onAwarenessUpdatePayload,\n onChangePayload,\n MessageReceiver,\n Debugger,\n onConfigurePayload,\n onListenPayload,\n beforeBroadcastStatelessPayload,\n} from '@hocuspocus/server'\nimport kleur from 'kleur'\n\nexport type RedisInstance = RedisClient.Cluster | RedisClient.Redis\n\nexport interface Configuration {\n /**\n * Redis port\n */\n port: number,\n /**\n * Redis host\n */\n host: string,\n /**\n * Redis Cluster\n */\n nodes?: ClusterNode[],\n /**\n * Duplicate from an existed Redis instance\n */\n redis?: RedisInstance,\n /**\n * Redis instance creator\n */\n createClient?: () => RedisInstance,\n /**\n * Options passed directly to Redis constructor\n *\n * https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options\n */\n options?: ClusterOptions | RedisOptions,\n /**\n * An unique instance name, required to filter messages in Redis.\n * If none is provided an unique id is generated.\n */\n identifier: string,\n /**\n * Namespace for Redis keys, if none is provided 'hocuspocus' is used\n */\n prefix: string,\n /**\n * The maximum time for the Redis lock in ms (in case it can’t be released).\n */\n lockTimeout: number,\n}\n\nexport class Redis implements Extension {\n /**\n * Make sure to give that extension a higher priority, so\n * the `onStoreDocument` hook is able to intercept the chain,\n * before documents are stored to the database.\n */\n priority = 1000\n\n configuration: Configuration = {\n port: 6379,\n host: '127.0.0.1',\n prefix: 'hocuspocus',\n identifier: `host-${uuid()}`,\n lockTimeout: 1000,\n }\n\n pub: RedisInstance\n\n sub: RedisInstance\n\n documents: Map<string, Document> = new Map()\n\n redlock: Redlock\n\n locks = new Map<string, Redlock.Lock>()\n\n logger: Debugger\n\n public constructor(configuration: Partial<Configuration>) {\n this.configuration = {\n ...this.configuration,\n ...configuration,\n }\n\n // We’ll replace that in the onConfigure hook with the global instance.\n this.logger = new Debugger()\n\n // Create Redis instance\n const {\n port,\n host,\n options,\n nodes,\n redis,\n createClient,\n } = this.configuration\n\n if (typeof createClient === 'function') {\n this.pub = createClient()\n this.sub = createClient()\n } else if (redis) {\n this.pub = redis.duplicate()\n this.sub = redis.duplicate()\n } else if (nodes && nodes.length > 0) {\n this.pub = new RedisClient.Cluster(nodes, options)\n this.sub = new RedisClient.Cluster(nodes, options)\n } else {\n this.pub = new RedisClient(port, host, options)\n this.sub = new RedisClient(port, host, options)\n }\n this.sub.on('pmessageBuffer', this.handleIncomingMessage)\n\n this.redlock = new Redlock([this.pub])\n }\n\n async onConfigure({ instance }: onConfigurePayload) {\n this.logger = instance.debugger\n }\n\n async onListen({ configuration }: onListenPayload) {\n if (configuration.quiet) {\n return\n }\n\n console.warn(` ${kleur.yellow('[BREAKING CHANGE] Wait, the Redis extension got an overhaul. The new Redis extension doesn’t persist data, it only syncs data between instances. Use @hocuspocus/extension-database to store your documents. It works well with the Redis extension.')}`)\n console.log()\n }\n\n private getKey(documentName: string) {\n return `${this.configuration.prefix}:${documentName}`\n }\n\n private pubKey(documentName: string) {\n return `${this.getKey(documentName)}:${this.configuration.identifier.replace(/:/g, '')}`\n }\n\n private subKey(documentName: string) {\n return `${this.getKey(documentName)}:*`\n }\n\n private lockKey(documentName: string) {\n return `${this.getKey(documentName)}:lock`\n }\n\n /**\n * Once a document is laoded, subscribe to the channel in Redis.\n */\n public async afterLoadDocument({ documentName, document }: afterLoadDocumentPayload) {\n this.documents.set(documentName, document)\n\n return new Promise((resolve, reject) => {\n // On document creation the node will connect to pub and sub channels\n // for the document.\n this.sub.psubscribe(this.subKey(documentName), async error => {\n if (error) {\n reject(error)\n return\n }\n\n this.publishFirstSyncStep(documentName, document)\n this.requestAwarenessFromOtherInstances(documentName)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Publish the first sync step through Redis.\n */\n private async publishFirstSyncStep(documentName: string, document: Document) {\n const syncMessage = new OutgoingMessage(documentName)\n .createSyncMessage()\n .writeFirstSyncStepFor(document)\n\n return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(syncMessage.toUint8Array()))\n }\n\n /**\n * Let’s ask Redis who is connected already.\n */\n private async requestAwarenessFromOtherInstances(documentName: string) {\n const awarenessMessage = new OutgoingMessage(documentName)\n .writeQueryAwareness()\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(awarenessMessage.toUint8Array()),\n )\n }\n\n /**\n * Before the document is stored, make sure to set a lock in Redis.\n * That’s meant to avoid conflicts with other instances trying to store the document.\n */\n async onStoreDocument({ documentName }: onStoreDocumentPayload) {\n // Attempt to acquire a lock and read lastReceivedTimestamp from Redis,\n // to avoid conflict with other instances storing the same document.\n return new Promise((resolve, reject) => {\n this.redlock.lock(this.lockKey(documentName), this.configuration.lockTimeout, async (error, lock) => {\n if (error || !lock) {\n // Expected behavior: Could not acquire lock, another instance locked it already.\n // No further `onStoreDocument` hooks will be executed.\n reject()\n return\n }\n\n this.locks.set(this.lockKey(documentName), lock)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Release the Redis lock, so other instances can store documents.\n */\n async afterStoreDocument({ documentName }: afterStoreDocumentPayload) {\n this.locks.get(this.lockKey(documentName))?.unlock()\n .catch(() => {\n // Not able to unlock Redis. The lock will expire after ${lockTimeout} ms.\n // console.error(`Not able to unlock Redis. The lock will expire after ${this.configuration.lockTimeout}ms.`)\n })\n .finally(() => {\n this.locks.delete(this.lockKey(documentName))\n })\n }\n\n /**\n * Handle awareness update messages received directly by this Hocuspocus instance.\n */\n async onAwarenessUpdate({\n documentName, awareness, added, updated, removed,\n }: onAwarenessUpdatePayload) {\n const changedClients = added.concat(updated, removed)\n const message = new OutgoingMessage(documentName)\n .createAwarenessUpdateMessage(awareness, changedClients)\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Handle incoming messages published on all subscribed document channels.\n * Note that this will also include messages from ourselves as it is not possible\n * in Redis to filter these.\n */\n private handleIncomingMessage = async (channel: Buffer, pattern: Buffer, data: Buffer) => {\n const message = new IncomingMessage(data)\n // we don't need the documentName from the message, we are just taking it from the redis channelName.\n // we have to immediately write it back to the encoder though, to make sure the structure of the message is correct\n message.writeVarString(message.readVarString())\n\n const channelName = pattern.toString()\n const [_, documentName, identifier] = channelName.split(':')\n const document = this.documents.get(documentName)\n\n if (identifier === this.configuration.identifier) {\n return\n }\n\n if (!document) {\n return\n }\n\n new MessageReceiver(\n message,\n this.logger,\n ).apply(document, undefined, reply => {\n return this.pub.publishBuffer(\n this.pubKey(document.name),\n Buffer.from(reply),\n )\n })\n }\n\n /**\n * if the ydoc changed, we'll need to inform other Hocuspocus servers about it.\n */\n public async onChange(data: onChangePayload): Promise<any> {\n return this.publishFirstSyncStep(data.documentName, data.document)\n }\n\n /**\n * Make sure to *not* listen for further changes, when there’s\n * noone connected anymore.\n */\n public onDisconnect = async ({ documentName, clientsCount }: onDisconnectPayload) => {\n // Do nothing, when other users are still connected to the document.\n if (clientsCount > 0) {\n return\n }\n\n // It was indeed the last connected user.\n this.documents.delete(documentName)\n\n // Time to end the subscription on the document channel.\n this.sub.punsubscribe(this.subKey(documentName), error => {\n if (error) {\n console.error(error)\n }\n })\n }\n\n async beforeBroadcastStateless(data: beforeBroadcastStatelessPayload) {\n const message = new OutgoingMessage(data.documentName)\n .writeBroadcastStateless(data.payload)\n\n return this.pub.publishBuffer(\n this.pubKey(data.documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Kill the Redlock connection immediately.\n */\n async onDestroy() {\n this.redlock.quit()\n }\n}\n"],"names":["uuid","IncomingMessage","MessageReceiver","Debugger","RedisClient","Redlock","kleur","OutgoingMessage"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import RedisClient from 'ioredis';
2
2
  import Redlock from 'redlock';
3
3
  import { v4 } from 'uuid';
4
- import { MessageReceiver, IncomingMessage, Debugger, OutgoingMessage } from '@hocuspocus/server';
4
+ import { IncomingMessage, MessageReceiver, Debugger, OutgoingMessage } from '@hocuspocus/server';
5
5
  import kleur from 'kleur';
6
6
 
7
7
  class Redis {
@@ -27,6 +27,10 @@ class Redis {
27
27
  * in Redis to filter these.
28
28
  */
29
29
  this.handleIncomingMessage = async (channel, pattern, data) => {
30
+ const message = new IncomingMessage(data);
31
+ // we don't need the documentName from the message, we are just taking it from the redis channelName.
32
+ // we have to immediately write it back to the encoder though, to make sure the structure of the message is correct
33
+ message.writeVarString(message.readVarString());
30
34
  const channelName = pattern.toString();
31
35
  const [_, documentName, identifier] = channelName.split(':');
32
36
  const document = this.documents.get(documentName);
@@ -36,7 +40,7 @@ class Redis {
36
40
  if (!document) {
37
41
  return;
38
42
  }
39
- new MessageReceiver(new IncomingMessage(data), this.logger).apply(document, undefined, reply => {
43
+ new MessageReceiver(message, this.logger).apply(document, undefined, reply => {
40
44
  return this.pub.publishBuffer(this.pubKey(document.name), Buffer.from(reply));
41
45
  });
42
46
  };
@@ -130,7 +134,7 @@ class Redis {
130
134
  * Publish the first sync step through Redis.
131
135
  */
132
136
  async publishFirstSyncStep(documentName, document) {
133
- const syncMessage = new OutgoingMessage()
137
+ const syncMessage = new OutgoingMessage(documentName)
134
138
  .createSyncMessage()
135
139
  .writeFirstSyncStepFor(document);
136
140
  return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(syncMessage.toUint8Array()));
@@ -139,7 +143,7 @@ class Redis {
139
143
  * Let’s ask Redis who is connected already.
140
144
  */
141
145
  async requestAwarenessFromOtherInstances(documentName) {
142
- const awarenessMessage = new OutgoingMessage()
146
+ const awarenessMessage = new OutgoingMessage(documentName)
143
147
  .writeQueryAwareness();
144
148
  return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(awarenessMessage.toUint8Array()));
145
149
  }
@@ -180,7 +184,7 @@ class Redis {
180
184
  */
181
185
  async onAwarenessUpdate({ documentName, awareness, added, updated, removed, }) {
182
186
  const changedClients = added.concat(updated, removed);
183
- const message = new OutgoingMessage()
187
+ const message = new OutgoingMessage(documentName)
184
188
  .createAwarenessUpdateMessage(awareness, changedClients);
185
189
  return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(message.toUint8Array()));
186
190
  }
@@ -191,7 +195,7 @@ class Redis {
191
195
  return this.publishFirstSyncStep(data.documentName, data.document);
192
196
  }
193
197
  async beforeBroadcastStateless(data) {
194
- const message = new OutgoingMessage()
198
+ const message = new OutgoingMessage(data.documentName)
195
199
  .writeBroadcastStateless(data.payload);
196
200
  return this.pub.publishBuffer(this.pubKey(data.documentName), Buffer.from(message.toUint8Array()));
197
201
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hocuspocus-redis.esm.js","sources":["../src/Redis.ts"],"sourcesContent":["import RedisClient, { ClusterNode, ClusterOptions, RedisOptions } from 'ioredis'\nimport Redlock from 'redlock'\nimport { v4 as uuid } from 'uuid'\nimport {\n IncomingMessage,\n OutgoingMessage,\n Document,\n Extension,\n afterLoadDocumentPayload,\n afterStoreDocumentPayload,\n onDisconnectPayload,\n onStoreDocumentPayload,\n onAwarenessUpdatePayload,\n onChangePayload,\n MessageReceiver,\n Debugger,\n onConfigurePayload,\n onListenPayload,\n beforeBroadcastStatelessPayload,\n} from '@hocuspocus/server'\nimport kleur from 'kleur'\n\nexport type RedisInstance = RedisClient.Cluster | RedisClient.Redis\n\nexport interface Configuration {\n /**\n * Redis port\n */\n port: number,\n /**\n * Redis host\n */\n host: string,\n /**\n * Redis Cluster\n */\n nodes?: ClusterNode[],\n /**\n * Duplicate from an existed Redis instance\n */\n redis?: RedisInstance,\n /**\n * Redis instance creator\n */\n createClient?: () => RedisInstance,\n /**\n * Options passed directly to Redis constructor\n *\n * https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options\n */\n options?: ClusterOptions | RedisOptions,\n /**\n * An unique instance name, required to filter messages in Redis.\n * If none is provided an unique id is generated.\n */\n identifier: string,\n /**\n * Namespace for Redis keys, if none is provided 'hocuspocus' is used\n */\n prefix: string,\n /**\n * The maximum time for the Redis lock in ms (in case it can’t be released).\n */\n lockTimeout: number,\n}\n\nexport class Redis implements Extension {\n /**\n * Make sure to give that extension a higher priority, so\n * the `onStoreDocument` hook is able to intercept the chain,\n * before documents are stored to the database.\n */\n priority = 1000\n\n configuration: Configuration = {\n port: 6379,\n host: '127.0.0.1',\n prefix: 'hocuspocus',\n identifier: `host-${uuid()}`,\n lockTimeout: 1000,\n }\n\n pub: RedisInstance\n\n sub: RedisInstance\n\n documents: Map<string, Document> = new Map()\n\n redlock: Redlock\n\n locks = new Map<string, Redlock.Lock>()\n\n logger: Debugger\n\n public constructor(configuration: Partial<Configuration>) {\n this.configuration = {\n ...this.configuration,\n ...configuration,\n }\n\n // We’ll replace that in the onConfigure hook with the global instance.\n this.logger = new Debugger()\n\n // Create Redis instance\n const {\n port,\n host,\n options,\n nodes,\n redis,\n createClient,\n } = this.configuration\n\n if (typeof createClient === 'function') {\n this.pub = createClient()\n this.sub = createClient()\n } else if (redis) {\n this.pub = redis.duplicate()\n this.sub = redis.duplicate()\n } else if (nodes && nodes.length > 0) {\n this.pub = new RedisClient.Cluster(nodes, options)\n this.sub = new RedisClient.Cluster(nodes, options)\n } else {\n this.pub = new RedisClient(port, host, options)\n this.sub = new RedisClient(port, host, options)\n }\n this.sub.on('pmessageBuffer', this.handleIncomingMessage)\n\n this.redlock = new Redlock([this.pub])\n }\n\n async onConfigure({ instance }: onConfigurePayload) {\n this.logger = instance.debugger\n }\n\n async onListen({ configuration }: onListenPayload) {\n if (configuration.quiet) {\n return\n }\n\n console.warn(` ${kleur.yellow('[BREAKING CHANGE] Wait, the Redis extension got an overhaul. The new Redis extension doesn’t persist data, it only syncs data between instances. Use @hocuspocus/extension-database to store your documents. It works well with the Redis extension.')}`)\n console.log()\n }\n\n private getKey(documentName: string) {\n return `${this.configuration.prefix}:${documentName}`\n }\n\n private pubKey(documentName: string) {\n return `${this.getKey(documentName)}:${this.configuration.identifier.replace(/:/g, '')}`\n }\n\n private subKey(documentName: string) {\n return `${this.getKey(documentName)}:*`\n }\n\n private lockKey(documentName: string) {\n return `${this.getKey(documentName)}:lock`\n }\n\n /**\n * Once a document is laoded, subscribe to the channel in Redis.\n */\n public async afterLoadDocument({ documentName, document }: afterLoadDocumentPayload) {\n this.documents.set(documentName, document)\n\n return new Promise((resolve, reject) => {\n // On document creation the node will connect to pub and sub channels\n // for the document.\n this.sub.psubscribe(this.subKey(documentName), async error => {\n if (error) {\n reject(error)\n return\n }\n\n this.publishFirstSyncStep(documentName, document)\n this.requestAwarenessFromOtherInstances(documentName)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Publish the first sync step through Redis.\n */\n private async publishFirstSyncStep(documentName: string, document: Document) {\n const syncMessage = new OutgoingMessage()\n .createSyncMessage()\n .writeFirstSyncStepFor(document)\n\n return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(syncMessage.toUint8Array()))\n }\n\n /**\n * Let’s ask Redis who is connected already.\n */\n private async requestAwarenessFromOtherInstances(documentName: string) {\n const awarenessMessage = new OutgoingMessage()\n .writeQueryAwareness()\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(awarenessMessage.toUint8Array()),\n )\n }\n\n /**\n * Before the document is stored, make sure to set a lock in Redis.\n * That’s meant to avoid conflicts with other instances trying to store the document.\n */\n async onStoreDocument({ documentName }: onStoreDocumentPayload) {\n // Attempt to acquire a lock and read lastReceivedTimestamp from Redis,\n // to avoid conflict with other instances storing the same document.\n return new Promise((resolve, reject) => {\n this.redlock.lock(this.lockKey(documentName), this.configuration.lockTimeout, async (error, lock) => {\n if (error || !lock) {\n // Expected behavior: Could not acquire lock, another instance locked it already.\n // No further `onStoreDocument` hooks will be executed.\n reject()\n return\n }\n\n this.locks.set(this.lockKey(documentName), lock)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Release the Redis lock, so other instances can store documents.\n */\n async afterStoreDocument({ documentName }: afterStoreDocumentPayload) {\n this.locks.get(this.lockKey(documentName))?.unlock()\n .catch(() => {\n // Not able to unlock Redis. The lock will expire after ${lockTimeout} ms.\n // console.error(`Not able to unlock Redis. The lock will expire after ${this.configuration.lockTimeout}ms.`)\n })\n .finally(() => {\n this.locks.delete(this.lockKey(documentName))\n })\n }\n\n /**\n * Handle awareness update messages received directly by this Hocuspocus instance.\n */\n async onAwarenessUpdate({\n documentName, awareness, added, updated, removed,\n }: onAwarenessUpdatePayload) {\n const changedClients = added.concat(updated, removed)\n const message = new OutgoingMessage()\n .createAwarenessUpdateMessage(awareness, changedClients)\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Handle incoming messages published on all subscribed document channels.\n * Note that this will also include messages from ourselves as it is not possible\n * in Redis to filter these.\n */\n private handleIncomingMessage = async (channel: Buffer, pattern: Buffer, data: Buffer) => {\n const channelName = pattern.toString()\n const [_, documentName, identifier] = channelName.split(':')\n const document = this.documents.get(documentName)\n\n if (identifier === this.configuration.identifier) {\n return\n }\n\n if (!document) {\n return\n }\n\n new MessageReceiver(\n new IncomingMessage(data),\n this.logger,\n ).apply(document, undefined, reply => {\n return this.pub.publishBuffer(\n this.pubKey(document.name),\n Buffer.from(reply),\n )\n })\n }\n\n /**\n * if the ydoc changed, we'll need to inform other Hocuspocus servers about it.\n */\n public async onChange(data: onChangePayload): Promise<any> {\n return this.publishFirstSyncStep(data.documentName, data.document)\n }\n\n /**\n * Make sure to *not* listen for further changes, when there’s\n * noone connected anymore.\n */\n public onDisconnect = async ({ documentName, clientsCount }: onDisconnectPayload) => {\n // Do nothing, when other users are still connected to the document.\n if (clientsCount > 0) {\n return\n }\n\n // It was indeed the last connected user.\n this.documents.delete(documentName)\n\n // Time to end the subscription on the document channel.\n this.sub.punsubscribe(this.subKey(documentName), error => {\n if (error) {\n console.error(error)\n }\n })\n }\n\n async beforeBroadcastStateless(data: beforeBroadcastStatelessPayload) {\n const message = new OutgoingMessage()\n .writeBroadcastStateless(data.payload)\n\n return this.pub.publishBuffer(\n this.pubKey(data.documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Kill the Redlock connection immediately.\n */\n async onDestroy() {\n this.redlock.quit()\n }\n}\n"],"names":["uuid"],"mappings":";;;;;;MAkEa,KAAK,CAAA;AA4BhB,IAAA,WAAA,CAAmB,aAAqC,EAAA;AA3BxD;;;;AAIG;QACH,IAAQ,CAAA,QAAA,GAAG,IAAI,CAAA;AAEf,QAAA,IAAA,CAAA,aAAa,GAAkB;AAC7B,YAAA,IAAI,EAAE,IAAI;AACV,YAAA,IAAI,EAAE,WAAW;AACjB,YAAA,MAAM,EAAE,YAAY;AACpB,YAAA,UAAU,EAAE,CAAA,KAAA,EAAQA,EAAI,EAAE,CAAE,CAAA;AAC5B,YAAA,WAAW,EAAE,IAAI;SAClB,CAAA;AAMD,QAAA,IAAA,CAAA,SAAS,GAA0B,IAAI,GAAG,EAAE,CAAA;AAI5C,QAAA,IAAA,CAAA,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAA;AA0KvC;;;;AAIE;QACM,IAAqB,CAAA,qBAAA,GAAG,OAAO,OAAe,EAAE,OAAe,EAAE,IAAY,KAAI;AACvF,YAAA,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAA;AACtC,YAAA,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,UAAU,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;AAEjD,YAAA,IAAI,UAAU,KAAK,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE;gBAChD,OAAM;AACP,aAAA;YAED,IAAI,CAAC,QAAQ,EAAE;gBACb,OAAM;AACP,aAAA;YAED,IAAI,eAAe,CACjB,IAAI,eAAe,CAAC,IAAI,CAAC,EACzB,IAAI,CAAC,MAAM,CACZ,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,IAAG;gBACnC,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAC1B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CACnB,CAAA;AACH,aAAC,CAAC,CAAA;AACJ,SAAC,CAAA;AASD;;;AAGG;QACI,IAAY,CAAA,YAAA,GAAG,OAAO,EAAE,YAAY,EAAE,YAAY,EAAuB,KAAI;;YAElF,IAAI,YAAY,GAAG,CAAC,EAAE;gBACpB,OAAM;AACP,aAAA;;AAGD,YAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;;AAGnC,YAAA,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,KAAK,IAAG;AACvD,gBAAA,IAAI,KAAK,EAAE;AACT,oBAAA,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AACrB,iBAAA;AACH,aAAC,CAAC,CAAA;AACJ,SAAC,CAAA;QA5NC,IAAI,CAAC,aAAa,GAAG;YACnB,GAAG,IAAI,CAAC,aAAa;AACrB,YAAA,GAAG,aAAa;SACjB,CAAA;;AAGD,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAA;;AAG5B,QAAA,MAAM,EACJ,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,KAAK,EACL,KAAK,EACL,YAAY,GACb,GAAG,IAAI,CAAC,aAAa,CAAA;AAEtB,QAAA,IAAI,OAAO,YAAY,KAAK,UAAU,EAAE;AACtC,YAAA,IAAI,CAAC,GAAG,GAAG,YAAY,EAAE,CAAA;AACzB,YAAA,IAAI,CAAC,GAAG,GAAG,YAAY,EAAE,CAAA;AAC1B,SAAA;AAAM,aAAA,IAAI,KAAK,EAAE;AAChB,YAAA,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAA;AAC5B,YAAA,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAA;AAC7B,SAAA;AAAM,aAAA,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;AACpC,YAAA,IAAI,CAAC,GAAG,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AAClD,YAAA,IAAI,CAAC,GAAG,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACnD,SAAA;AAAM,aAAA;AACL,YAAA,IAAI,CAAC,GAAG,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;AAC/C,YAAA,IAAI,CAAC,GAAG,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;AAChD,SAAA;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAA;AAEzD,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;KACvC;AAED,IAAA,MAAM,WAAW,CAAC,EAAE,QAAQ,EAAsB,EAAA;AAChD,QAAA,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAA;KAChC;AAED,IAAA,MAAM,QAAQ,CAAC,EAAE,aAAa,EAAmB,EAAA;QAC/C,IAAI,aAAa,CAAC,KAAK,EAAE;YACvB,OAAM;AACP,SAAA;AAED,QAAA,OAAO,CAAC,IAAI,CAAC,CAAA,EAAA,EAAK,KAAK,CAAC,MAAM,CAAC,sPAAsP,CAAC,CAAE,CAAA,CAAC,CAAA;QACzR,OAAO,CAAC,GAAG,EAAE,CAAA;KACd;AAEO,IAAA,MAAM,CAAC,YAAoB,EAAA;QACjC,OAAO,CAAA,EAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAA,CAAA,EAAI,YAAY,CAAA,CAAE,CAAA;KACtD;AAEO,IAAA,MAAM,CAAC,YAAoB,EAAA;QACjC,OAAO,CAAA,EAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA,CAAE,CAAA;KACzF;AAEO,IAAA,MAAM,CAAC,YAAoB,EAAA;QACjC,OAAO,CAAA,EAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAA;KACxC;AAEO,IAAA,OAAO,CAAC,YAAoB,EAAA;QAClC,OAAO,CAAA,EAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAA;KAC3C;AAED;;AAEG;AACI,IAAA,MAAM,iBAAiB,CAAC,EAAE,YAAY,EAAE,QAAQ,EAA4B,EAAA;QACjF,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAA;QAE1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,KAAI;;;AAGrC,YAAA,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,OAAM,KAAK,KAAG;AAC3D,gBAAA,IAAI,KAAK,EAAE;oBACT,MAAM,CAAC,KAAK,CAAC,CAAA;oBACb,OAAM;AACP,iBAAA;AAED,gBAAA,IAAI,CAAC,oBAAoB,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAA;AACjD,gBAAA,IAAI,CAAC,kCAAkC,CAAC,YAAY,CAAC,CAAA;gBAErD,OAAO,CAAC,SAAS,CAAC,CAAA;AACpB,aAAC,CAAC,CAAA;AACJ,SAAC,CAAC,CAAA;KACH;AAED;;AAEG;AACK,IAAA,MAAM,oBAAoB,CAAC,YAAoB,EAAE,QAAkB,EAAA;AACzE,QAAA,MAAM,WAAW,GAAG,IAAI,eAAe,EAAE;AACtC,aAAA,iBAAiB,EAAE;aACnB,qBAAqB,CAAC,QAAQ,CAAC,CAAA;QAElC,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;KAClG;AAED;;AAEG;IACK,MAAM,kCAAkC,CAAC,YAAoB,EAAA;AACnE,QAAA,MAAM,gBAAgB,GAAG,IAAI,eAAe,EAAE;AAC3C,aAAA,mBAAmB,EAAE,CAAA;QAExB,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAC3B,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EACzB,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC,CAC7C,CAAA;KACF;AAED;;;AAGG;AACH,IAAA,MAAM,eAAe,CAAC,EAAE,YAAY,EAA0B,EAAA;;;QAG5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,KAAI;YACrC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,OAAO,KAAK,EAAE,IAAI,KAAI;AAClG,gBAAA,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE;;;AAGlB,oBAAA,MAAM,EAAE,CAAA;oBACR,OAAM;AACP,iBAAA;AAED,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,CAAA;gBAEhD,OAAO,CAAC,SAAS,CAAC,CAAA;AACpB,aAAC,CAAC,CAAA;AACJ,SAAC,CAAC,CAAA;KACH;AAED;;AAEG;AACH,IAAA,MAAM,kBAAkB,CAAC,EAAE,YAAY,EAA6B,EAAA;;AAClE,QAAA,CAAA,EAAA,GAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,MAAE,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,MAAM,GAC/C,KAAK,CAAC,MAAK;;;AAGZ,SAAC,CACA,CAAA,OAAO,CAAC,MAAK;AACZ,YAAA,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAA;AAC/C,SAAC,CAAC,CAAA;KACL;AAED;;AAEG;AACH,IAAA,MAAM,iBAAiB,CAAC,EACtB,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GACvB,EAAA;QACzB,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AACrD,QAAA,MAAM,OAAO,GAAG,IAAI,eAAe,EAAE;AAClC,aAAA,4BAA4B,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;QAE1D,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAC3B,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EACzB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CACpC,CAAA;KACF;AA+BD;;AAEG;IACI,MAAM,QAAQ,CAAC,IAAqB,EAAA;AACzC,QAAA,OAAO,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;KACnE;IAuBD,MAAM,wBAAwB,CAAC,IAAqC,EAAA;AAClE,QAAA,MAAM,OAAO,GAAG,IAAI,eAAe,EAAE;AAClC,aAAA,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAExC,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CACpC,CAAA;KACF;AAED;;AAEG;AACH,IAAA,MAAM,SAAS,GAAA;AACb,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;KACpB;AACF;;;;"}
1
+ {"version":3,"file":"hocuspocus-redis.esm.js","sources":["../src/Redis.ts"],"sourcesContent":["import RedisClient, { ClusterNode, ClusterOptions, RedisOptions } from 'ioredis'\nimport Redlock from 'redlock'\nimport { v4 as uuid } from 'uuid'\nimport {\n IncomingMessage,\n OutgoingMessage,\n Document,\n Extension,\n afterLoadDocumentPayload,\n afterStoreDocumentPayload,\n onDisconnectPayload,\n onStoreDocumentPayload,\n onAwarenessUpdatePayload,\n onChangePayload,\n MessageReceiver,\n Debugger,\n onConfigurePayload,\n onListenPayload,\n beforeBroadcastStatelessPayload,\n} from '@hocuspocus/server'\nimport kleur from 'kleur'\n\nexport type RedisInstance = RedisClient.Cluster | RedisClient.Redis\n\nexport interface Configuration {\n /**\n * Redis port\n */\n port: number,\n /**\n * Redis host\n */\n host: string,\n /**\n * Redis Cluster\n */\n nodes?: ClusterNode[],\n /**\n * Duplicate from an existed Redis instance\n */\n redis?: RedisInstance,\n /**\n * Redis instance creator\n */\n createClient?: () => RedisInstance,\n /**\n * Options passed directly to Redis constructor\n *\n * https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options\n */\n options?: ClusterOptions | RedisOptions,\n /**\n * An unique instance name, required to filter messages in Redis.\n * If none is provided an unique id is generated.\n */\n identifier: string,\n /**\n * Namespace for Redis keys, if none is provided 'hocuspocus' is used\n */\n prefix: string,\n /**\n * The maximum time for the Redis lock in ms (in case it can’t be released).\n */\n lockTimeout: number,\n}\n\nexport class Redis implements Extension {\n /**\n * Make sure to give that extension a higher priority, so\n * the `onStoreDocument` hook is able to intercept the chain,\n * before documents are stored to the database.\n */\n priority = 1000\n\n configuration: Configuration = {\n port: 6379,\n host: '127.0.0.1',\n prefix: 'hocuspocus',\n identifier: `host-${uuid()}`,\n lockTimeout: 1000,\n }\n\n pub: RedisInstance\n\n sub: RedisInstance\n\n documents: Map<string, Document> = new Map()\n\n redlock: Redlock\n\n locks = new Map<string, Redlock.Lock>()\n\n logger: Debugger\n\n public constructor(configuration: Partial<Configuration>) {\n this.configuration = {\n ...this.configuration,\n ...configuration,\n }\n\n // We’ll replace that in the onConfigure hook with the global instance.\n this.logger = new Debugger()\n\n // Create Redis instance\n const {\n port,\n host,\n options,\n nodes,\n redis,\n createClient,\n } = this.configuration\n\n if (typeof createClient === 'function') {\n this.pub = createClient()\n this.sub = createClient()\n } else if (redis) {\n this.pub = redis.duplicate()\n this.sub = redis.duplicate()\n } else if (nodes && nodes.length > 0) {\n this.pub = new RedisClient.Cluster(nodes, options)\n this.sub = new RedisClient.Cluster(nodes, options)\n } else {\n this.pub = new RedisClient(port, host, options)\n this.sub = new RedisClient(port, host, options)\n }\n this.sub.on('pmessageBuffer', this.handleIncomingMessage)\n\n this.redlock = new Redlock([this.pub])\n }\n\n async onConfigure({ instance }: onConfigurePayload) {\n this.logger = instance.debugger\n }\n\n async onListen({ configuration }: onListenPayload) {\n if (configuration.quiet) {\n return\n }\n\n console.warn(` ${kleur.yellow('[BREAKING CHANGE] Wait, the Redis extension got an overhaul. The new Redis extension doesn’t persist data, it only syncs data between instances. Use @hocuspocus/extension-database to store your documents. It works well with the Redis extension.')}`)\n console.log()\n }\n\n private getKey(documentName: string) {\n return `${this.configuration.prefix}:${documentName}`\n }\n\n private pubKey(documentName: string) {\n return `${this.getKey(documentName)}:${this.configuration.identifier.replace(/:/g, '')}`\n }\n\n private subKey(documentName: string) {\n return `${this.getKey(documentName)}:*`\n }\n\n private lockKey(documentName: string) {\n return `${this.getKey(documentName)}:lock`\n }\n\n /**\n * Once a document is laoded, subscribe to the channel in Redis.\n */\n public async afterLoadDocument({ documentName, document }: afterLoadDocumentPayload) {\n this.documents.set(documentName, document)\n\n return new Promise((resolve, reject) => {\n // On document creation the node will connect to pub and sub channels\n // for the document.\n this.sub.psubscribe(this.subKey(documentName), async error => {\n if (error) {\n reject(error)\n return\n }\n\n this.publishFirstSyncStep(documentName, document)\n this.requestAwarenessFromOtherInstances(documentName)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Publish the first sync step through Redis.\n */\n private async publishFirstSyncStep(documentName: string, document: Document) {\n const syncMessage = new OutgoingMessage(documentName)\n .createSyncMessage()\n .writeFirstSyncStepFor(document)\n\n return this.pub.publishBuffer(this.pubKey(documentName), Buffer.from(syncMessage.toUint8Array()))\n }\n\n /**\n * Let’s ask Redis who is connected already.\n */\n private async requestAwarenessFromOtherInstances(documentName: string) {\n const awarenessMessage = new OutgoingMessage(documentName)\n .writeQueryAwareness()\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(awarenessMessage.toUint8Array()),\n )\n }\n\n /**\n * Before the document is stored, make sure to set a lock in Redis.\n * That’s meant to avoid conflicts with other instances trying to store the document.\n */\n async onStoreDocument({ documentName }: onStoreDocumentPayload) {\n // Attempt to acquire a lock and read lastReceivedTimestamp from Redis,\n // to avoid conflict with other instances storing the same document.\n return new Promise((resolve, reject) => {\n this.redlock.lock(this.lockKey(documentName), this.configuration.lockTimeout, async (error, lock) => {\n if (error || !lock) {\n // Expected behavior: Could not acquire lock, another instance locked it already.\n // No further `onStoreDocument` hooks will be executed.\n reject()\n return\n }\n\n this.locks.set(this.lockKey(documentName), lock)\n\n resolve(undefined)\n })\n })\n }\n\n /**\n * Release the Redis lock, so other instances can store documents.\n */\n async afterStoreDocument({ documentName }: afterStoreDocumentPayload) {\n this.locks.get(this.lockKey(documentName))?.unlock()\n .catch(() => {\n // Not able to unlock Redis. The lock will expire after ${lockTimeout} ms.\n // console.error(`Not able to unlock Redis. The lock will expire after ${this.configuration.lockTimeout}ms.`)\n })\n .finally(() => {\n this.locks.delete(this.lockKey(documentName))\n })\n }\n\n /**\n * Handle awareness update messages received directly by this Hocuspocus instance.\n */\n async onAwarenessUpdate({\n documentName, awareness, added, updated, removed,\n }: onAwarenessUpdatePayload) {\n const changedClients = added.concat(updated, removed)\n const message = new OutgoingMessage(documentName)\n .createAwarenessUpdateMessage(awareness, changedClients)\n\n return this.pub.publishBuffer(\n this.pubKey(documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Handle incoming messages published on all subscribed document channels.\n * Note that this will also include messages from ourselves as it is not possible\n * in Redis to filter these.\n */\n private handleIncomingMessage = async (channel: Buffer, pattern: Buffer, data: Buffer) => {\n const message = new IncomingMessage(data)\n // we don't need the documentName from the message, we are just taking it from the redis channelName.\n // we have to immediately write it back to the encoder though, to make sure the structure of the message is correct\n message.writeVarString(message.readVarString())\n\n const channelName = pattern.toString()\n const [_, documentName, identifier] = channelName.split(':')\n const document = this.documents.get(documentName)\n\n if (identifier === this.configuration.identifier) {\n return\n }\n\n if (!document) {\n return\n }\n\n new MessageReceiver(\n message,\n this.logger,\n ).apply(document, undefined, reply => {\n return this.pub.publishBuffer(\n this.pubKey(document.name),\n Buffer.from(reply),\n )\n })\n }\n\n /**\n * if the ydoc changed, we'll need to inform other Hocuspocus servers about it.\n */\n public async onChange(data: onChangePayload): Promise<any> {\n return this.publishFirstSyncStep(data.documentName, data.document)\n }\n\n /**\n * Make sure to *not* listen for further changes, when there’s\n * noone connected anymore.\n */\n public onDisconnect = async ({ documentName, clientsCount }: onDisconnectPayload) => {\n // Do nothing, when other users are still connected to the document.\n if (clientsCount > 0) {\n return\n }\n\n // It was indeed the last connected user.\n this.documents.delete(documentName)\n\n // Time to end the subscription on the document channel.\n this.sub.punsubscribe(this.subKey(documentName), error => {\n if (error) {\n console.error(error)\n }\n })\n }\n\n async beforeBroadcastStateless(data: beforeBroadcastStatelessPayload) {\n const message = new OutgoingMessage(data.documentName)\n .writeBroadcastStateless(data.payload)\n\n return this.pub.publishBuffer(\n this.pubKey(data.documentName),\n Buffer.from(message.toUint8Array()),\n )\n }\n\n /**\n * Kill the Redlock connection immediately.\n */\n async onDestroy() {\n this.redlock.quit()\n }\n}\n"],"names":["uuid"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { IncomingMessage, ServerResponse } from 'http';
3
- import WebSocket, { WebSocketServer } from 'ws';
4
3
  import { Socket } from 'net';
4
+ import WebSocket, { WebSocketServer } from 'ws';
5
5
  import { Storage } from './Storage';
6
6
  export interface Configuration {
7
7
  password: string | undefined;
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
- import { Extension, onChangePayload, onConfigurePayload, onLoadDocumentPayload, onDisconnectPayload, onRequestPayload, onUpgradePayload, connectedPayload } from '@hocuspocus/server';
3
2
  import { IncomingMessage, ServerResponse } from 'http';
3
+ import { Extension, onChangePayload, onConfigurePayload, onLoadDocumentPayload, onDisconnectPayload, onRequestPayload, onUpgradePayload, connectedPayload } from '@hocuspocus/server';
4
4
  import WebSocket from 'ws';
5
5
  import { Storage } from './Storage';
6
6
  import { Dashboard } from './Dashboard';
@@ -1,5 +1,6 @@
1
1
  import { HocuspocusProvider, HocuspocusProviderConfiguration } from './HocuspocusProvider';
2
- export declare type HocuspocusCloudProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & AdditionalHocuspocusCloudProviderConfiguration;
2
+ import { HocuspocusProviderWebsocketConfiguration } from './HocuspocusProviderWebsocket';
3
+ export declare type HocuspocusCloudProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & Partial<Pick<HocuspocusProviderWebsocketConfiguration, 'url'>> & AdditionalHocuspocusCloudProviderConfiguration;
3
4
  export interface AdditionalHocuspocusCloudProviderConfiguration {
4
5
  /**
5
6
  * A Hocuspocus Cloud key, get one here: https://hocuspocus.cloud/
@@ -1,16 +1,13 @@
1
1
  import * as Y from 'yjs';
2
2
  import { Awareness } from 'y-protocols/awareness';
3
3
  import * as mutex from 'lib0/mutex';
4
- import type { Event, CloseEvent, MessageEvent } from 'ws';
4
+ import type { CloseEvent, Event, MessageEvent } from 'ws';
5
5
  import EventEmitter from './EventEmitter';
6
6
  import { ConstructableOutgoingMessage, onAuthenticationFailedParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatelessParameters, onStatusParameters, onSyncedParameters, WebSocketStatus } from './types';
7
+ import { HocuspocusProviderWebsocket } from './HocuspocusProviderWebsocket';
7
8
  import { onAwarenessChangeParameters, onAwarenessUpdateParameters } from '.';
8
- export declare type HocuspocusProviderConfiguration = Required<Pick<CompleteHocuspocusProviderConfiguration, 'url' | 'name'>> & Partial<CompleteHocuspocusProviderConfiguration>;
9
+ export declare type HocuspocusProviderConfiguration = Required<Pick<CompleteHocuspocusProviderConfiguration, 'name' | 'websocketProvider'>> & Partial<CompleteHocuspocusProviderConfiguration>;
9
10
  export interface CompleteHocuspocusProviderConfiguration {
10
- /**
11
- * URL of your @hocuspocus/server instance
12
- */
13
- url: string;
14
11
  /**
15
12
  * The identifier/name of your document
16
13
  */
@@ -19,10 +16,6 @@ export interface CompleteHocuspocusProviderConfiguration {
19
16
  * The actual Y.js document
20
17
  */
21
18
  document: Y.Doc;
22
- /**
23
- * Pass `false` to start the connection manually.
24
- */
25
- connect: boolean;
26
19
  /**
27
20
  * Pass false to disable broadcasting between browser tabs.
28
21
  */
@@ -42,49 +35,13 @@ export interface CompleteHocuspocusProviderConfiguration {
42
35
  [key: string]: any;
43
36
  };
44
37
  /**
45
- * An optional WebSocket polyfill, for example for Node.js
38
+ * Hocuspocus websocket provider
46
39
  */
47
- WebSocketPolyfill: any;
40
+ websocketProvider: HocuspocusProviderWebsocket;
48
41
  /**
49
42
  * Force syncing the document in the defined interval.
50
43
  */
51
44
  forceSyncInterval: false | number;
52
- /**
53
- * Disconnect when no message is received for the defined amount of milliseconds.
54
- */
55
- messageReconnectTimeout: number;
56
- /**
57
- * The delay between each attempt in milliseconds. You can provide a factor to have the delay grow exponentially.
58
- */
59
- delay: number;
60
- /**
61
- * The intialDelay is the amount of time to wait before making the first attempt. This option should typically be 0 since you typically want the first attempt to happen immediately.
62
- */
63
- initialDelay: number;
64
- /**
65
- * The factor option is used to grow the delay exponentially.
66
- */
67
- factor: number;
68
- /**
69
- * The maximum number of attempts or 0 if there is no limit on number of attempts.
70
- */
71
- maxAttempts: number;
72
- /**
73
- * minDelay is used to set a lower bound of delay when jitter is enabled. This property has no effect if jitter is disabled.
74
- */
75
- minDelay: number;
76
- /**
77
- * The maxDelay option is used to set an upper bound for the delay when factor is enabled. A value of 0 can be provided if there should be no upper bound when calculating delay.
78
- */
79
- maxDelay: number;
80
- /**
81
- * If jitter is true then the calculated delay will be a random integer value between minDelay and the calculated delay for the current iteration.
82
- */
83
- jitter: boolean;
84
- /**
85
- * A timeout in milliseconds. If timeout is non-zero then a timer is set using setTimeout. If the timeout is triggered then future attempts will be aborted.
86
- */
87
- timeout: number;
88
45
  onAuthenticated: () => void;
89
46
  onAuthenticationFailed: (data: onAuthenticationFailedParameters) => void;
90
47
  onOpen: (data: onOpenParameters) => void;
@@ -107,32 +64,18 @@ export interface CompleteHocuspocusProviderConfiguration {
107
64
  export declare class HocuspocusProvider extends EventEmitter {
108
65
  configuration: CompleteHocuspocusProviderConfiguration;
109
66
  subscribedToBroadcastChannel: boolean;
110
- webSocket: WebSocket | null;
111
- shouldConnect: boolean;
112
- status: WebSocketStatus;
113
67
  isSynced: boolean;
114
68
  unsyncedChanges: number;
69
+ status: WebSocketStatus;
115
70
  isAuthenticated: boolean;
116
- lastMessageReceived: number;
117
71
  mux: mutex.mutex;
118
72
  intervals: any;
119
- connectionAttempt: {
120
- resolve: (value?: any) => void;
121
- reject: (reason?: any) => void;
122
- } | null;
123
73
  constructor(configuration: HocuspocusProviderConfiguration);
74
+ onStatus({ status }: onStatusParameters): void;
124
75
  setConfiguration(configuration?: Partial<HocuspocusProviderConfiguration>): void;
125
- boundConnect: () => Promise<unknown>;
126
- cancelWebsocketRetry?: () => void;
127
- connect(): Promise<unknown>;
128
- createWebSocketConnection(): Promise<unknown>;
129
- resolveConnectionAttempt(): void;
130
- stopConnectionAttempt(): void;
131
- rejectConnectionAttempt(): void;
132
76
  get document(): Y.Doc;
133
77
  get awareness(): Awareness;
134
78
  get hasUnsyncedChanges(): boolean;
135
- checkConnection(): void;
136
79
  forceSync(): void;
137
80
  boundBeforeUnload: () => void;
138
81
  beforeUnload(): void;
@@ -140,22 +83,21 @@ export declare class HocuspocusProvider extends EventEmitter {
140
83
  sendStateless(payload: string): void;
141
84
  documentUpdateHandler(update: Uint8Array, origin: any): void;
142
85
  awarenessUpdateHandler({ added, updated, removed }: any, origin: any): void;
143
- permissionDeniedHandler(reason: string): void;
144
- authenticatedHandler(): void;
145
- get serverUrl(): string;
146
- get url(): string;
147
86
  get synced(): boolean;
148
87
  set synced(state: boolean);
149
88
  receiveStateless(payload: string): void;
150
89
  get isAuthenticationRequired(): boolean;
90
+ connect(): Promise<unknown>;
151
91
  disconnect(): void;
152
92
  onOpen(event: Event): Promise<void>;
153
93
  getToken(): Promise<string | null>;
154
94
  startSync(): void;
155
- send(Message: ConstructableOutgoingMessage, args: any, broadcast?: boolean): void;
95
+ send(message: ConstructableOutgoingMessage, args: any, broadcast?: boolean): void;
156
96
  onMessage(event: MessageEvent): void;
157
97
  onClose(event: CloseEvent): void;
158
98
  destroy(): void;
99
+ permissionDeniedHandler(reason: string): void;
100
+ authenticatedHandler(): void;
159
101
  get broadcastChannel(): string;
160
102
  boundBroadcastChannelSubscriber: (data: ArrayBuffer) => void;
161
103
  broadcastChannelSubscriber(data: ArrayBuffer): void;
@@ -0,0 +1,115 @@
1
+ import * as mutex from 'lib0/mutex';
2
+ import type { MessageEvent } from 'ws';
3
+ import { Event } from 'ws';
4
+ import EventEmitter from './EventEmitter';
5
+ import { onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatusParameters, WebSocketStatus } from './types';
6
+ import { HocuspocusProvider, onAwarenessChangeParameters, onAwarenessUpdateParameters } from '.';
7
+ export declare type HocuspocusProviderWebsocketConfiguration = Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, 'url'>> & Partial<CompleteHocuspocusProviderWebsocketConfiguration>;
8
+ export interface CompleteHocuspocusProviderWebsocketConfiguration {
9
+ /**
10
+ * URL of your @hocuspocus/server instance
11
+ */
12
+ url: string;
13
+ /**
14
+ * Pass `false` to start the connection manually.
15
+ */
16
+ connect: boolean;
17
+ /**
18
+ * URL parameters that should be added.
19
+ */
20
+ parameters: {
21
+ [key: string]: any;
22
+ };
23
+ /**
24
+ * An optional WebSocket polyfill, for example for Node.js
25
+ */
26
+ WebSocketPolyfill: any;
27
+ /**
28
+ * Disconnect when no message is received for the defined amount of milliseconds.
29
+ */
30
+ messageReconnectTimeout: number;
31
+ /**
32
+ * The delay between each attempt in milliseconds. You can provide a factor to have the delay grow exponentially.
33
+ */
34
+ delay: number;
35
+ /**
36
+ * The intialDelay is the amount of time to wait before making the first attempt. This option should typically be 0 since you typically want the first attempt to happen immediately.
37
+ */
38
+ initialDelay: number;
39
+ /**
40
+ * The factor option is used to grow the delay exponentially.
41
+ */
42
+ factor: number;
43
+ /**
44
+ * The maximum number of attempts or 0 if there is no limit on number of attempts.
45
+ */
46
+ maxAttempts: number;
47
+ /**
48
+ * minDelay is used to set a lower bound of delay when jitter is enabled. This property has no effect if jitter is disabled.
49
+ */
50
+ minDelay: number;
51
+ /**
52
+ * The maxDelay option is used to set an upper bound for the delay when factor is enabled. A value of 0 can be provided if there should be no upper bound when calculating delay.
53
+ */
54
+ maxDelay: number;
55
+ /**
56
+ * If jitter is true then the calculated delay will be a random integer value between minDelay and the calculated delay for the current iteration.
57
+ */
58
+ jitter: boolean;
59
+ /**
60
+ * A timeout in milliseconds. If timeout is non-zero then a timer is set using setTimeout. If the timeout is triggered then future attempts will be aborted.
61
+ */
62
+ timeout: number;
63
+ onOpen: (data: onOpenParameters) => void;
64
+ onConnect: () => void;
65
+ onMessage: (data: onMessageParameters) => void;
66
+ onOutgoingMessage: (data: onOutgoingMessageParameters) => void;
67
+ onStatus: (data: onStatusParameters) => void;
68
+ onDisconnect: (data: onDisconnectParameters) => void;
69
+ onClose: (data: onCloseParameters) => void;
70
+ onDestroy: () => void;
71
+ onAwarenessUpdate: (data: onAwarenessUpdateParameters) => void;
72
+ onAwarenessChange: (data: onAwarenessChangeParameters) => void;
73
+ /**
74
+ * Don’t output any warnings.
75
+ */
76
+ quiet: boolean;
77
+ }
78
+ export declare class HocuspocusProviderWebsocket extends EventEmitter {
79
+ configuration: CompleteHocuspocusProviderWebsocketConfiguration;
80
+ subscribedToBroadcastChannel: boolean;
81
+ webSocket: WebSocket | null;
82
+ shouldConnect: boolean;
83
+ status: WebSocketStatus;
84
+ lastMessageReceived: number;
85
+ mux: mutex.mutex;
86
+ intervals: any;
87
+ connectionAttempt: {
88
+ resolve: (value?: any) => void;
89
+ reject: (reason?: any) => void;
90
+ } | null;
91
+ constructor(configuration: HocuspocusProviderWebsocketConfiguration);
92
+ receivedOnOpenPayload?: Event | undefined;
93
+ receivedOnStatusPayload?: onStatusParameters | undefined;
94
+ onOpen(event: Event): Promise<void>;
95
+ onStatus(data: onStatusParameters): Promise<void>;
96
+ attach(provider: HocuspocusProvider): void;
97
+ detach(provider: HocuspocusProvider): void;
98
+ setConfiguration(configuration?: Partial<HocuspocusProviderWebsocketConfiguration>): void;
99
+ boundConnect: () => Promise<unknown>;
100
+ cancelWebsocketRetry?: () => void;
101
+ connect(): Promise<unknown>;
102
+ createWebSocketConnection(): Promise<unknown>;
103
+ onMessage(event: MessageEvent): void;
104
+ resolveConnectionAttempt(): void;
105
+ stopConnectionAttempt(): void;
106
+ rejectConnectionAttempt(): void;
107
+ checkConnection(): void;
108
+ registerEventListeners(): void;
109
+ get serverUrl(): string;
110
+ get url(): string;
111
+ disconnect(): void;
112
+ send(message: any): void;
113
+ onClose({ event }: onCloseParameters): void;
114
+ destroy(): void;
115
+ }
@@ -7,8 +7,10 @@ export declare class IncomingMessage {
7
7
  decoder: Decoder;
8
8
  constructor(data: any);
9
9
  readVarUint(): MessageType;
10
+ readVarString(): string;
10
11
  readVarUint8Array(): Uint8Array;
11
12
  writeVarUint(type: MessageType): void;
13
+ writeVarString(string: string): void;
12
14
  writeVarUint8Array(data: Uint8Array): void;
13
15
  length(): number;
14
16
  }
@@ -0,0 +1,8 @@
1
+ import * as encoding from 'lib0/encoding';
2
+ import { MessageType, OutgoingMessageArguments } from '../types';
3
+ import { OutgoingMessage } from '../OutgoingMessage';
4
+ export declare class CloseMessage extends OutgoingMessage {
5
+ type: MessageType;
6
+ description: string;
7
+ get(args: Partial<OutgoingMessageArguments>): encoding.Encoder;
8
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './HocuspocusProvider';
2
2
  export * from './HocuspocusCloudProvider';
3
+ export * from './HocuspocusProviderWebsocket';
3
4
  export * from './types';
@@ -15,7 +15,8 @@ export declare enum MessageType {
15
15
  Awareness = 1,
16
16
  Auth = 2,
17
17
  QueryAwareness = 3,
18
- Stateless = 5
18
+ Stateless = 5,
19
+ CLOSE = 7
19
20
  }
20
21
  export declare enum WebSocketStatus {
21
22
  Connecting = "connecting",
@@ -27,6 +28,7 @@ export interface OutgoingMessageInterface {
27
28
  type?: MessageType;
28
29
  }
29
30
  export interface OutgoingMessageArguments {
31
+ documentName: string;
30
32
  token: string;
31
33
  document: Y.Doc;
32
34
  awareness: Awareness;
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
+ import { IncomingMessage as HTTPIncomingMessage } from 'http';
2
3
  import AsyncLock from 'async-lock';
3
4
  import WebSocket from 'ws';
4
- import { IncomingMessage as HTTPIncomingMessage } from 'http';
5
5
  import { CloseEvent } from '@hocuspocus/common';
6
6
  import Document from './Document';
7
7
  import { Debugger } from './Debugger';
@@ -23,10 +23,14 @@ export declare class Connection {
23
23
  * Constructor.
24
24
  */
25
25
  constructor(connection: WebSocket, request: HTTPIncomingMessage, document: Document, timeout: number, socketId: string, context: any, readOnly: boolean | undefined, logger: Debugger);
26
+ boundClose: (event?: CloseEvent | undefined) => void;
27
+ boundHandleMessage: (data: Uint8Array) => void;
28
+ boundHandlePong: () => void;
29
+ handlePong(): void;
26
30
  /**
27
31
  * Set a callback that will be triggered when the connection is closed
28
32
  */
29
- onClose(callback: (document: Document) => void): Connection;
33
+ onClose(callback: (document: Document, event?: CloseEvent) => void): Connection;
30
34
  /**
31
35
  * Set a callback that will be triggered when an stateless message is received
32
36
  */
@@ -62,15 +66,5 @@ export declare class Connection {
62
66
  * @private
63
67
  */
64
68
  private handleMessage;
65
- /**
66
- * Get the underlying connection instance
67
- * @deprecated
68
- */
69
- get instance(): WebSocket;
70
- /**
71
- * Get the underlying connection instance
72
- * @deprecated
73
- */
74
- get connection(): WebSocket;
75
69
  }
76
70
  export default Connection;
@@ -1,10 +1,9 @@
1
1
  /// <reference types="node" />
2
- import WebSocket, { AddressInfo, WebSocketServer } from 'ws';
3
2
  import { IncomingMessage, Server as HTTPServer } from 'http';
4
- import { Configuration, HookName, HookPayload } from './types';
3
+ import WebSocket, { AddressInfo, WebSocketServer } from 'ws';
4
+ import { Configuration, HookName, HookPayload, onListenPayload } from './types';
5
5
  import Document from './Document';
6
6
  import { Debugger } from './Debugger';
7
- import { onListenPayload } from '.';
8
7
  export declare const defaultConfiguration: {
9
8
  name: null;
10
9
  port: number;
@@ -68,7 +67,7 @@ export declare class Hocuspocus {
68
67
  * … and if nothings fails it’ll fully establish the connection and
69
68
  * load the Document then.
70
69
  */
71
- handleConnection(incoming: WebSocket, request: IncomingMessage, documentName: string, context?: any): void;
70
+ handleConnection(incoming: WebSocket, request: IncomingMessage, context?: any): void;
72
71
  /**
73
72
  * Handle update of the given document
74
73
  */
@@ -98,10 +97,6 @@ export declare class Hocuspocus {
98
97
  * Get parameters by the given request
99
98
  */
100
99
  private static getParameters;
101
- /**
102
- * Get document name by the given request
103
- */
104
- private getDocumentNameFromRequest;
105
100
  enableDebugging(): void;
106
101
  enableMessageLogging(): void;
107
102
  disableLogging(): void;
@@ -16,5 +16,6 @@ export declare class IncomingMessage {
16
16
  readVarString(): string;
17
17
  toUint8Array(): Uint8Array;
18
18
  writeVarUint(type: MessageType): void;
19
+ writeVarString(string: string): void;
19
20
  get length(): number;
20
21
  }
@@ -1,4 +1,3 @@
1
- import { Awareness } from 'y-protocols/awareness';
2
1
  import Connection from './Connection';
3
2
  import { IncomingMessage } from './IncomingMessage';
4
3
  import { Debugger } from './Debugger';
@@ -9,5 +8,5 @@ export declare class MessageReceiver {
9
8
  constructor(message: IncomingMessage, logger: Debugger);
10
9
  apply(document: Document, connection?: Connection, reply?: (message: Uint8Array) => void): void;
11
10
  readSyncMessage(message: IncomingMessage, document: Document, connection?: Connection, reply?: (message: Uint8Array) => void, requestFirstSync?: boolean): 0 | 2 | 1;
12
- applyQueryAwarenessMessage(awareness: Awareness, reply?: (message: Uint8Array) => void): void;
11
+ applyQueryAwarenessMessage(document: Document, reply?: (message: Uint8Array) => void): void;
13
12
  }
@@ -5,7 +5,7 @@ export declare class OutgoingMessage {
5
5
  encoder: Encoder;
6
6
  type?: number;
7
7
  category?: string;
8
- constructor();
8
+ constructor(documentName: string);
9
9
  createSyncMessage(): OutgoingMessage;
10
10
  createSyncReplyMessage(): OutgoingMessage;
11
11
  createAwarenessUpdateMessage(awareness: Awareness, changedClients?: Array<any>): OutgoingMessage;
@@ -13,7 +13,8 @@ export declare enum MessageType {
13
13
  QueryAwareness = 3,
14
14
  SyncReply = 4,
15
15
  Stateless = 5,
16
- BroadcastStateless = 6
16
+ BroadcastStateless = 6,
17
+ CLOSE = 7
17
18
  }
18
19
  export interface AwarenessUpdate {
19
20
  added: Array<any>;
@@ -2,6 +2,7 @@ export * from './createDirectory';
2
2
  export * from './flushRedis';
3
3
  export * from './newHocuspocus';
4
4
  export * from './newHocuspocusProvider';
5
+ export * from './newHocuspocusProviderWebsocket';
5
6
  export * from './randomInteger';
6
7
  export * from './redisConnectionSettings';
7
8
  export * from './removeDirectory';
@@ -1,3 +1,3 @@
1
- import { HocuspocusProvider, HocuspocusProviderConfiguration } from '@hocuspocus/provider';
1
+ import { HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocketConfiguration } from '@hocuspocus/provider';
2
2
  import { Hocuspocus } from '@hocuspocus/server';
3
- export declare const newHocuspocusProvider: (server: Hocuspocus, options?: Partial<Omit<HocuspocusProviderConfiguration, 'url'>>) => HocuspocusProvider;
3
+ export declare const newHocuspocusProvider: (server: Hocuspocus, options?: Partial<HocuspocusProviderConfiguration>, websocketOptions?: Partial<HocuspocusProviderWebsocketConfiguration>) => HocuspocusProvider;
@@ -0,0 +1,3 @@
1
+ import { HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration } from '@hocuspocus/provider';
2
+ import { Hocuspocus } from '@hocuspocus/server';
3
+ export declare const newHocuspocusProviderWebsocket: (server: Hocuspocus, options?: Partial<Omit<HocuspocusProviderWebsocketConfiguration, 'url'>>) => HocuspocusProviderWebsocket;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hocuspocus/extension-redis",
3
- "version": "1.1.1",
3
+ "version": "2.0.0-alpha.1",
4
4
  "description": "Scale Hocuspocus horizontally with Redis",
5
5
  "homepage": "https://hocuspocus.dev",
6
6
  "keywords": [
@@ -32,7 +32,7 @@
32
32
  "@types/redlock": "^4.0.3"
33
33
  },
34
34
  "dependencies": {
35
- "@hocuspocus/server": "^1.1.1",
35
+ "@hocuspocus/server": "^2.0.0-alpha.1",
36
36
  "ioredis": "^4.28.2",
37
37
  "kleur": "^4.1.4",
38
38
  "lodash.debounce": "^4.0.8",
package/src/Redis.ts CHANGED
@@ -185,7 +185,7 @@ export class Redis implements Extension {
185
185
  * Publish the first sync step through Redis.
186
186
  */
187
187
  private async publishFirstSyncStep(documentName: string, document: Document) {
188
- const syncMessage = new OutgoingMessage()
188
+ const syncMessage = new OutgoingMessage(documentName)
189
189
  .createSyncMessage()
190
190
  .writeFirstSyncStepFor(document)
191
191
 
@@ -196,7 +196,7 @@ export class Redis implements Extension {
196
196
  * Let’s ask Redis who is connected already.
197
197
  */
198
198
  private async requestAwarenessFromOtherInstances(documentName: string) {
199
- const awarenessMessage = new OutgoingMessage()
199
+ const awarenessMessage = new OutgoingMessage(documentName)
200
200
  .writeQueryAwareness()
201
201
 
202
202
  return this.pub.publishBuffer(
@@ -249,7 +249,7 @@ export class Redis implements Extension {
249
249
  documentName, awareness, added, updated, removed,
250
250
  }: onAwarenessUpdatePayload) {
251
251
  const changedClients = added.concat(updated, removed)
252
- const message = new OutgoingMessage()
252
+ const message = new OutgoingMessage(documentName)
253
253
  .createAwarenessUpdateMessage(awareness, changedClients)
254
254
 
255
255
  return this.pub.publishBuffer(
@@ -264,6 +264,11 @@ export class Redis implements Extension {
264
264
  * in Redis to filter these.
265
265
  */
266
266
  private handleIncomingMessage = async (channel: Buffer, pattern: Buffer, data: Buffer) => {
267
+ const message = new IncomingMessage(data)
268
+ // we don't need the documentName from the message, we are just taking it from the redis channelName.
269
+ // we have to immediately write it back to the encoder though, to make sure the structure of the message is correct
270
+ message.writeVarString(message.readVarString())
271
+
267
272
  const channelName = pattern.toString()
268
273
  const [_, documentName, identifier] = channelName.split(':')
269
274
  const document = this.documents.get(documentName)
@@ -277,7 +282,7 @@ export class Redis implements Extension {
277
282
  }
278
283
 
279
284
  new MessageReceiver(
280
- new IncomingMessage(data),
285
+ message,
281
286
  this.logger,
282
287
  ).apply(document, undefined, reply => {
283
288
  return this.pub.publishBuffer(
@@ -316,7 +321,7 @@ export class Redis implements Extension {
316
321
  }
317
322
 
318
323
  async beforeBroadcastStateless(data: beforeBroadcastStatelessPayload) {
319
- const message = new OutgoingMessage()
324
+ const message = new OutgoingMessage(data.documentName)
320
325
  .writeBroadcastStateless(data.payload)
321
326
 
322
327
  return this.pub.publishBuffer(
@@ -1 +0,0 @@
1
- export {};