diff --git a/README.md b/README.md index 5e7a7b0..74cf80b 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,20 @@ wsOpts = {
Fires when the underlying websocket connection closes with an error. It forwards the websocket event to this event handler.
+### Base64 Transport Encoding + +Some WebSocket servers, such as AWS WebSocket APIs, do not support binary data. To enable compatibility, you can set the `useBase64` option in the constructor options. When enabled, all document updates and awareness information are encoded as base64 strings instead of binary. + +**Example:** + +```js +const wsProvider = new WebsocketProvider('ws://your-server', 'room', doc, { + useBase64: true +}) +``` + +Set `useBase64: true` if your WebSocket server does not support binary data. This ensures all communication is safely transmitted as base64-encoded strings. + ## License [The MIT License](./LICENSE) © Kevin Jahns diff --git a/package.json b/package.json index f104724..19a2732 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@y/websocket", + "name": "@adobe/y-websocket", "version": "4.0.0-1", "description": "Websockets provider for Yjs", "main": "./dist/y-websocket.cjs", diff --git a/src/y-websocket.js b/src/y-websocket.js index 69ae05f..b599da5 100644 --- a/src/y-websocket.js +++ b/src/y-websocket.js @@ -16,6 +16,7 @@ import { ObservableV2 } from 'lib0/observable' import * as math from 'lib0/math' import * as url from 'lib0/url' import * as env from 'lib0/environment' +import { toBase64, fromBase64 } from 'lib0/buffer' export const messageSync = 0 export const messageQueryAwareness = 3 @@ -179,12 +180,13 @@ const setupWS = (provider) => { provider.wsconnecting = true provider.wsconnected = false provider.synced = false + const { txEncode, txDecode } = provider websocket.onmessage = (event) => { provider.wsLastMessageReceived = time.getUnixTime() - const encoder = readMessage(provider, new Uint8Array(event.data), true) + const encoder = readMessage(provider, txDecode(event.data), true) if (encoding.length(encoder) > 1) { - websocket.send(encoding.toUint8Array(encoder)) + websocket.send(txEncode(encoding.toUint8Array(encoder))) } } websocket.onerror = (event) => { @@ -205,7 +207,7 @@ const setupWS = (provider) => { const encoder = encoding.createEncoder() encoding.writeVarUint(encoder, messageSync) syncProtocol.writeSyncStep1(encoder, provider.doc) - websocket.send(encoding.toUint8Array(encoder)) + websocket.send(txEncode(encoding.toUint8Array(encoder))) // broadcast local awareness state if (provider.awareness.getLocalState() !== null) { const encoderAwarenessState = encoding.createEncoder() @@ -216,7 +218,7 @@ const setupWS = (provider) => { provider.doc.clientID ]) ) - websocket.send(encoding.toUint8Array(encoderAwarenessState)) + websocket.send(txEncode(encoding.toUint8Array(encoderAwarenessState))) } } provider.emit('status', [{ @@ -227,12 +229,12 @@ const setupWS = (provider) => { /** * @param {WebsocketProvider} provider - * @param {ArrayBuffer} buf + * @param {Uint8Array} buf */ const broadcastMessage = (provider, buf) => { const ws = provider.ws if (provider.wsconnected && ws && ws.readyState === ws.OPEN) { - ws.send(buf) + ws.send(provider.txEncode(buf)) } if (provider.bcconnected) { bc.publish(provider.bcChannel, buf, provider) @@ -266,6 +268,7 @@ export class WebsocketProvider extends ObservableV2 { * @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds * @param {number} [opts.maxBackoffTime] Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff) * @param {boolean} [opts.disableBc] Disable cross-tab BroadcastChannel communication + * @param {boolean} [opts.useBase64] Enable base64 transport encoding and decoding */ constructor (serverUrl, roomname, doc, { connect = true, @@ -275,7 +278,8 @@ export class WebsocketProvider extends ObservableV2 { WebSocketPolyfill = WebSocket, resyncInterval = -1, maxBackoffTime = 2500, - disableBc = false + disableBc = false, + useBase64 = false } = {}) { super() // ensure that serverUrl does not end with / @@ -302,6 +306,14 @@ export class WebsocketProvider extends ObservableV2 { this.disableBc = disableBc this.wsUnsuccessfulReconnects = 0 this.messageHandlers = messageHandlers.slice() + this.useBase64 = useBase64 + this.txDecode = useBase64 + ? fromBase64 + : (data) => new Uint8Array(data) + this.txEncode = useBase64 + ? toBase64 + : (data) => data + /** * @type {boolean} */ @@ -328,7 +340,7 @@ export class WebsocketProvider extends ObservableV2 { const encoder = encoding.createEncoder() encoding.writeVarUint(encoder, messageSync) syncProtocol.writeSyncStep1(encoder, doc) - this.ws.send(encoding.toUint8Array(encoder)) + this.ws.send(this.txEncode(encoding.toUint8Array(encoder))) } }, resyncInterval)) } @@ -388,7 +400,7 @@ export class WebsocketProvider extends ObservableV2 { if ( this.wsconnected && messageReconnectTimeout < - time.getUnixTime() - this.wsLastMessageReceived + time.getUnixTime() - this.wsLastMessageReceived ) { // no message received in a long time - not even your own awareness // updates (which are updated every 15 seconds)