rte-rich-text-editor-ws 1.0.0
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.
- package/README.md +159 -0
- package/package.json +38 -0
- package/rte-ws.js +292 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# RTE-WS — WebSocket Connector for RTE Rich Text Editor
|
|
2
|
+
|
|
3
|
+
Add real-time auto-save and multi-user collaboration to [RTE](https://www.npmjs.com/package/rte-rich-text-editor) with a single script tag. **Zero dependencies.**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### npm
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install rte-rich-text-editor-ws
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Script Tag
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script src="https://rte.whitneys.co/rte.js"></script>
|
|
19
|
+
<script src="https://rte.whitneys.co/rte-ws.js"></script>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<div id="editor"></div>
|
|
26
|
+
|
|
27
|
+
<script src="https://rte.whitneys.co/rte.js"></script>
|
|
28
|
+
<script src="https://rte.whitneys.co/rte-ws.js"></script>
|
|
29
|
+
<script>
|
|
30
|
+
const editor = RTE.init('#editor');
|
|
31
|
+
|
|
32
|
+
const ws = RTEWS.connect(editor, 'wss://yourserver.com/ws', {
|
|
33
|
+
docId: 'doc-123',
|
|
34
|
+
userId: 'user-abc',
|
|
35
|
+
onOpen: () => console.log('Connected'),
|
|
36
|
+
onSaved: (msg) => console.log('Saved, version:', msg.version),
|
|
37
|
+
onRemoteUpdate: (msg) => console.log('Update from:', msg.userId),
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### CommonJS
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
const RTE = require('rte-rich-text-editor');
|
|
46
|
+
const RTEWS = require('rte-rich-text-editor-ws');
|
|
47
|
+
|
|
48
|
+
const editor = RTE.init('#editor');
|
|
49
|
+
const ws = RTEWS.connect(editor, 'wss://yourserver.com/ws', { docId: 'doc-1', userId: 'user-1' });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
| Feature | Description |
|
|
55
|
+
|---|---|
|
|
56
|
+
| **Auto-Save** | Debounced content sync to backend on every change |
|
|
57
|
+
| **Collaboration** | Broadcast and receive changes between multiple users |
|
|
58
|
+
| **Auto-Reconnect** | Exponential backoff (1s → 2s → 4s → ... up to 30s) |
|
|
59
|
+
| **Heartbeat** | Configurable keep-alive ping (default 30s) |
|
|
60
|
+
| **Cursor Preservation** | Local cursor position saved/restored on remote updates |
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const ws = RTEWS.connect(editor, 'wss://yourserver.com/ws', {
|
|
66
|
+
docId: 'doc-123', // Document identifier
|
|
67
|
+
userId: 'user-abc', // User identifier
|
|
68
|
+
debounceMs: 1000, // Debounce delay before sending changes
|
|
69
|
+
autoSave: true, // Auto-send changes on editor input
|
|
70
|
+
reconnect: true, // Auto-reconnect on disconnect
|
|
71
|
+
reconnectBaseMs: 1000, // Initial reconnect delay
|
|
72
|
+
reconnectMaxMs: 30000, // Max reconnect delay
|
|
73
|
+
heartbeatMs: 30000, // Ping interval (0 to disable)
|
|
74
|
+
onOpen: (ws) => {}, // WebSocket connected
|
|
75
|
+
onClose: (e) => {}, // WebSocket closed
|
|
76
|
+
onError: (e) => {}, // Error occurred
|
|
77
|
+
onSaved: (msg) => {}, // Server confirmed save
|
|
78
|
+
onRemoteUpdate: (msg) => {}, // Remote user change applied
|
|
79
|
+
onMessage: (msg) => {}, // Any incoming message
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
| Method / Property | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `ws.save()` | Send explicit save request |
|
|
88
|
+
| `ws.send(data)` | Send custom JSON message |
|
|
89
|
+
| `ws.disconnect()` | Close connection, stop reconnecting |
|
|
90
|
+
| `ws.reconnect()` | Manually reconnect |
|
|
91
|
+
| `ws.state` | `"connecting"`, `"open"`, `"closing"`, or `"closed"` |
|
|
92
|
+
| `ws.socket` | Raw WebSocket instance |
|
|
93
|
+
|
|
94
|
+
## Message Protocol
|
|
95
|
+
|
|
96
|
+
### Outgoing (Client → Server)
|
|
97
|
+
|
|
98
|
+
| Type | Fields | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `"join"` | `docId`, `userId` | Sent on connect |
|
|
101
|
+
| `"change"` | `docId`, `userId`, `html`, `text`, `words`, `chars` | Editor content changed |
|
|
102
|
+
| `"save"` | `docId`, `userId`, `html`, `text`, `words`, `chars` | Explicit save |
|
|
103
|
+
| `"ping"` | — | Heartbeat |
|
|
104
|
+
|
|
105
|
+
### Incoming (Server → Client)
|
|
106
|
+
|
|
107
|
+
| Type | Fields | Description |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `"load"` | `html` | Load initial content |
|
|
110
|
+
| `"update"` | `html`, `userId` | Remote user change |
|
|
111
|
+
| `"saved"` | `version` (optional) | Save confirmed |
|
|
112
|
+
| `"error"` | `message` | Server error |
|
|
113
|
+
|
|
114
|
+
## Backend Example (Node.js)
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import { WebSocketServer } from 'ws';
|
|
118
|
+
|
|
119
|
+
const wss = new WebSocketServer({ port: 8080 });
|
|
120
|
+
const docs = new Map();
|
|
121
|
+
|
|
122
|
+
wss.on('connection', (socket) => {
|
|
123
|
+
let docId = null, userId = null;
|
|
124
|
+
|
|
125
|
+
socket.on('message', (raw) => {
|
|
126
|
+
const msg = JSON.parse(raw);
|
|
127
|
+
|
|
128
|
+
if (msg.type === 'join') {
|
|
129
|
+
docId = msg.docId; userId = msg.userId;
|
|
130
|
+
if (!docs.has(docId)) docs.set(docId, { html: '', clients: new Set() });
|
|
131
|
+
docs.get(docId).clients.add(socket);
|
|
132
|
+
socket.send(JSON.stringify({ type: 'load', html: docs.get(docId).html }));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (msg.type === 'change' && docId) {
|
|
136
|
+
docs.get(docId).html = msg.html;
|
|
137
|
+
docs.get(docId).clients.forEach(c => {
|
|
138
|
+
if (c !== socket && c.readyState === 1)
|
|
139
|
+
c.send(JSON.stringify({ type: 'update', html: msg.html, userId }));
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (msg.type === 'save')
|
|
144
|
+
socket.send(JSON.stringify({ type: 'saved', version: Date.now() }));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
socket.on('close', () => {
|
|
148
|
+
if (docId && docs.has(docId)) docs.get(docId).clients.delete(socket);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Documentation
|
|
154
|
+
|
|
155
|
+
Full documentation with more backend examples at [rte.whitneys.co/websocket](https://rte.whitneys.co/websocket)
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rte-rich-text-editor-ws",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "WebSocket connector for RTE Rich Text Editor. Auto-save and real-time collaboration. Zero dependencies.",
|
|
5
|
+
"main": "rte-ws.js",
|
|
6
|
+
"browser": "rte-ws.js",
|
|
7
|
+
"unpkg": "rte-ws.js",
|
|
8
|
+
"jsdelivr": "rte-ws.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"rte-ws.js",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"rte",
|
|
15
|
+
"rich-text-editor",
|
|
16
|
+
"websocket",
|
|
17
|
+
"real-time",
|
|
18
|
+
"collaboration",
|
|
19
|
+
"auto-save",
|
|
20
|
+
"wysiwyg",
|
|
21
|
+
"editor",
|
|
22
|
+
"standalone",
|
|
23
|
+
"no-dependencies"
|
|
24
|
+
],
|
|
25
|
+
"author": "phpMyDEV, LLC",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/MIR-2025/rte.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/MIR-2025/rte/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://rte.whitneys.co/websocket",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"rte-rich-text-editor": ">=1.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/rte-ws.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RTE-WS — WebSocket Connector for RTE Rich Text Editor
|
|
3
|
+
* Standalone companion file. Works with any WebSocket backend.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Auto-save: debounced content sync to backend on changes
|
|
7
|
+
* - Real-time collaboration: broadcast and receive changes between users
|
|
8
|
+
* - Auto-reconnect with exponential backoff
|
|
9
|
+
* - Heartbeat / keep-alive ping
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* <script src="rte.js"></script>
|
|
13
|
+
* <script src="rte-ws.js"></script>
|
|
14
|
+
* <script>
|
|
15
|
+
* const editor = RTE.init('#editor');
|
|
16
|
+
* const ws = RTEWS.connect(editor, 'wss://yourserver.com/ws', {
|
|
17
|
+
* docId: 'doc-123',
|
|
18
|
+
* userId: 'user-abc',
|
|
19
|
+
* });
|
|
20
|
+
* </script>
|
|
21
|
+
*
|
|
22
|
+
* Backend messages (JSON):
|
|
23
|
+
* Incoming:
|
|
24
|
+
* { type: "load", html: "<p>...</p>" } — load initial content
|
|
25
|
+
* { type: "update", html: "<p>...</p>", userId: "..." } — remote user change
|
|
26
|
+
* { type: "cursor", userId: "...", position: {...} } — remote cursor (optional)
|
|
27
|
+
* { type: "saved", version: 5 } — server confirmed save
|
|
28
|
+
* { type: "error", message: "..." } — server error
|
|
29
|
+
*
|
|
30
|
+
* Outgoing:
|
|
31
|
+
* { type: "join", docId, userId } — join document
|
|
32
|
+
* { type: "change", docId, userId, html, text, words, chars } — content changed
|
|
33
|
+
* { type: "save", docId, userId, html, text, words, chars } — explicit save
|
|
34
|
+
* { type: "ping" } — heartbeat
|
|
35
|
+
*/
|
|
36
|
+
(function (root, factory) {
|
|
37
|
+
"use strict";
|
|
38
|
+
if (typeof define === "function" && define.amd) {
|
|
39
|
+
define([], factory);
|
|
40
|
+
} else if (typeof module === "object" && module.exports) {
|
|
41
|
+
module.exports = factory();
|
|
42
|
+
} else {
|
|
43
|
+
root.RTEWS = factory();
|
|
44
|
+
}
|
|
45
|
+
})(typeof self !== "undefined" ? self : this, function () {
|
|
46
|
+
"use strict";
|
|
47
|
+
|
|
48
|
+
const DEFAULTS = {
|
|
49
|
+
docId: null,
|
|
50
|
+
userId: null,
|
|
51
|
+
debounceMs: 1000,
|
|
52
|
+
reconnect: true,
|
|
53
|
+
reconnectMaxMs: 30000,
|
|
54
|
+
reconnectBaseMs: 1000,
|
|
55
|
+
heartbeatMs: 30000,
|
|
56
|
+
autoSave: true,
|
|
57
|
+
onOpen: null,
|
|
58
|
+
onClose: null,
|
|
59
|
+
onError: null,
|
|
60
|
+
onSaved: null,
|
|
61
|
+
onRemoteUpdate: null,
|
|
62
|
+
onMessage: null,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function connect(editor, url, opts) {
|
|
66
|
+
const cfg = Object.assign({}, DEFAULTS, opts);
|
|
67
|
+
let ws = null;
|
|
68
|
+
let reconnectAttempts = 0;
|
|
69
|
+
let reconnectTimer = null;
|
|
70
|
+
let heartbeatTimer = null;
|
|
71
|
+
let debounceTimer = null;
|
|
72
|
+
let isSending = false;
|
|
73
|
+
let destroyed = false;
|
|
74
|
+
let lastSentHTML = "";
|
|
75
|
+
|
|
76
|
+
// ── WebSocket lifecycle ──────────────────────────────
|
|
77
|
+
|
|
78
|
+
function open() {
|
|
79
|
+
if (destroyed) return;
|
|
80
|
+
ws = new WebSocket(url);
|
|
81
|
+
|
|
82
|
+
ws.onopen = function () {
|
|
83
|
+
reconnectAttempts = 0;
|
|
84
|
+
// Join document
|
|
85
|
+
send({ type: "join", docId: cfg.docId, userId: cfg.userId });
|
|
86
|
+
// Start heartbeat
|
|
87
|
+
startHeartbeat();
|
|
88
|
+
if (cfg.onOpen) cfg.onOpen(ws);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
ws.onmessage = function (e) {
|
|
92
|
+
let msg;
|
|
93
|
+
try {
|
|
94
|
+
msg = JSON.parse(e.data);
|
|
95
|
+
} catch (_) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
handleMessage(msg);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
ws.onclose = function (e) {
|
|
102
|
+
stopHeartbeat();
|
|
103
|
+
if (cfg.onClose) cfg.onClose(e);
|
|
104
|
+
if (cfg.reconnect && !destroyed) scheduleReconnect();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
ws.onerror = function (e) {
|
|
108
|
+
if (cfg.onError) cfg.onError(e);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function send(data) {
|
|
113
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
114
|
+
ws.send(JSON.stringify(data));
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Incoming messages ────────────────────────────────
|
|
121
|
+
|
|
122
|
+
function handleMessage(msg) {
|
|
123
|
+
switch (msg.type) {
|
|
124
|
+
case "load":
|
|
125
|
+
if (msg.html != null) {
|
|
126
|
+
editor.setHTML(msg.html);
|
|
127
|
+
lastSentHTML = msg.html;
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case "update":
|
|
132
|
+
// Remote user change — only apply if from a different user
|
|
133
|
+
if (msg.userId !== cfg.userId && msg.html != null) {
|
|
134
|
+
// Save cursor position
|
|
135
|
+
const sel = window.getSelection();
|
|
136
|
+
const hadFocus = document.activeElement === editor.element;
|
|
137
|
+
let savedOffset = null;
|
|
138
|
+
if (hadFocus && sel.rangeCount) {
|
|
139
|
+
const range = sel.getRangeAt(0);
|
|
140
|
+
savedOffset = {
|
|
141
|
+
startContainer: range.startContainer,
|
|
142
|
+
startOffset: range.startOffset,
|
|
143
|
+
endContainer: range.endContainer,
|
|
144
|
+
endOffset: range.endOffset,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
isSending = true;
|
|
148
|
+
editor.setHTML(msg.html);
|
|
149
|
+
lastSentHTML = msg.html;
|
|
150
|
+
isSending = false;
|
|
151
|
+
// Restore cursor
|
|
152
|
+
if (hadFocus && savedOffset) {
|
|
153
|
+
try {
|
|
154
|
+
const r = document.createRange();
|
|
155
|
+
r.setStart(savedOffset.startContainer, savedOffset.startOffset);
|
|
156
|
+
r.setEnd(savedOffset.endContainer, savedOffset.endOffset);
|
|
157
|
+
sel.removeAllRanges();
|
|
158
|
+
sel.addRange(r);
|
|
159
|
+
} catch (_) {
|
|
160
|
+
// Node may no longer exist after HTML replacement
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (cfg.onRemoteUpdate) cfg.onRemoteUpdate(msg);
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case "saved":
|
|
168
|
+
if (cfg.onSaved) cfg.onSaved(msg);
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case "error":
|
|
172
|
+
if (cfg.onError) cfg.onError(new Error(msg.message));
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (cfg.onMessage) cfg.onMessage(msg);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Auto-save on change ──────────────────────────────
|
|
180
|
+
|
|
181
|
+
function onEditorChange(data) {
|
|
182
|
+
if (isSending || !cfg.autoSave) return;
|
|
183
|
+
const html = editor.getHTML();
|
|
184
|
+
if (html === lastSentHTML) return;
|
|
185
|
+
|
|
186
|
+
clearTimeout(debounceTimer);
|
|
187
|
+
debounceTimer = setTimeout(function () {
|
|
188
|
+
lastSentHTML = html;
|
|
189
|
+
send({
|
|
190
|
+
type: "change",
|
|
191
|
+
docId: cfg.docId,
|
|
192
|
+
userId: cfg.userId,
|
|
193
|
+
html: html,
|
|
194
|
+
text: data.text,
|
|
195
|
+
words: data.words,
|
|
196
|
+
chars: data.chars,
|
|
197
|
+
});
|
|
198
|
+
}, cfg.debounceMs);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Hook into editor onChange
|
|
202
|
+
const origOnChange = editor.onChange;
|
|
203
|
+
editor.onChange = function (data) {
|
|
204
|
+
if (origOnChange) origOnChange(data);
|
|
205
|
+
onEditorChange(data);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// ── Reconnect ────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
function scheduleReconnect() {
|
|
211
|
+
if (destroyed) return;
|
|
212
|
+
reconnectAttempts++;
|
|
213
|
+
const delay = Math.min(
|
|
214
|
+
cfg.reconnectBaseMs * Math.pow(2, reconnectAttempts - 1),
|
|
215
|
+
cfg.reconnectMaxMs
|
|
216
|
+
);
|
|
217
|
+
reconnectTimer = setTimeout(open, delay);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Heartbeat ────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
function startHeartbeat() {
|
|
223
|
+
stopHeartbeat();
|
|
224
|
+
if (cfg.heartbeatMs > 0) {
|
|
225
|
+
heartbeatTimer = setInterval(function () {
|
|
226
|
+
send({ type: "ping" });
|
|
227
|
+
}, cfg.heartbeatMs);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function stopHeartbeat() {
|
|
232
|
+
if (heartbeatTimer) {
|
|
233
|
+
clearInterval(heartbeatTimer);
|
|
234
|
+
heartbeatTimer = null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Public API ───────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
open();
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
/** Send an explicit save request */
|
|
244
|
+
save: function () {
|
|
245
|
+
const html = editor.getHTML();
|
|
246
|
+
const text = editor.getText();
|
|
247
|
+
lastSentHTML = html;
|
|
248
|
+
return send({
|
|
249
|
+
type: "save",
|
|
250
|
+
docId: cfg.docId,
|
|
251
|
+
userId: cfg.userId,
|
|
252
|
+
html: html,
|
|
253
|
+
text: text,
|
|
254
|
+
words: text.split(/\s+/).filter(Boolean).length,
|
|
255
|
+
chars: text.length,
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
/** Send a custom message */
|
|
260
|
+
send: send,
|
|
261
|
+
|
|
262
|
+
/** Disconnect and stop reconnecting */
|
|
263
|
+
disconnect: function () {
|
|
264
|
+
destroyed = true;
|
|
265
|
+
clearTimeout(reconnectTimer);
|
|
266
|
+
clearTimeout(debounceTimer);
|
|
267
|
+
stopHeartbeat();
|
|
268
|
+
if (ws) ws.close();
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
/** Reconnect manually */
|
|
272
|
+
reconnect: function () {
|
|
273
|
+
destroyed = false;
|
|
274
|
+
if (ws) ws.close();
|
|
275
|
+
open();
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
/** Get connection state: "connecting" | "open" | "closing" | "closed" */
|
|
279
|
+
get state() {
|
|
280
|
+
if (!ws) return "closed";
|
|
281
|
+
return ["connecting", "open", "closing", "closed"][ws.readyState];
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
/** The raw WebSocket instance */
|
|
285
|
+
get socket() {
|
|
286
|
+
return ws;
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { connect: connect };
|
|
292
|
+
});
|