@cocreate/crdt 1.3.14 → 1.3.18
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/CHANGELOG.md +28 -0
- package/package.json +4 -4
- package/src/index.js +52 -56
- package/src/archive/core.1.js +0 -460
- package/src/archive/core.2.js +0 -321
- package/src/archive/index.1.js +0 -225
- package/src/archive/index.2.js +0 -230
- package/src/archive/lib111/y-websocket.js +0 -367
package/src/archive/index.2.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import CoCreateCrdt from "./core.js"
|
|
2
|
-
import * as Y from 'yjs'
|
|
3
|
-
import crud from '@cocreate/crud-client';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class CoCreateCRDTClass extends CoCreateCrdt
|
|
7
|
-
{
|
|
8
|
-
constructor(org, doc) {
|
|
9
|
-
super(org, doc)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
crdt.init({
|
|
14
|
-
collection: "module",
|
|
15
|
-
document_id: "",
|
|
16
|
-
name: "",
|
|
17
|
-
element: dom_object,
|
|
18
|
-
metadata: "xxxx"
|
|
19
|
-
})
|
|
20
|
-
*/
|
|
21
|
-
init(info) {
|
|
22
|
-
try {
|
|
23
|
-
// this.__validateKeysJson(info, ['collection', 'document_id', 'name']);
|
|
24
|
-
|
|
25
|
-
const id = this.__getYDocId(info['collection'], info['document_id'], info['name'])
|
|
26
|
-
|
|
27
|
-
if (!id) return;
|
|
28
|
-
this.createDoc(id)
|
|
29
|
-
|
|
30
|
-
} catch(e) {
|
|
31
|
-
console.log('Invalid param', e);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/*
|
|
36
|
-
crdt.replaceText({
|
|
37
|
-
collection: "module",
|
|
38
|
-
document_id: "",
|
|
39
|
-
name: "",
|
|
40
|
-
value: "",
|
|
41
|
-
updateCrud: true | false,
|
|
42
|
-
element: dom_object,
|
|
43
|
-
metadata: "xxxx"
|
|
44
|
-
})
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
replaceText(info){
|
|
48
|
-
if (!info) return;
|
|
49
|
-
|
|
50
|
-
let id = this.generateID(config.organization_Id, info['collection'], info['document_id'], info['name'])
|
|
51
|
-
let info1 = this.parseType(id)
|
|
52
|
-
|
|
53
|
-
if (!id) return;
|
|
54
|
-
|
|
55
|
-
if (info.updateCrud != false) info.updateCrud = true;
|
|
56
|
-
|
|
57
|
-
if (this.getType(id) ) {
|
|
58
|
-
let oldData = this.getType(id).toString();
|
|
59
|
-
let textValue = info.value.toString();
|
|
60
|
-
if (oldData && oldData.length > 0) {
|
|
61
|
-
this.deleteText(info['collection'], info['document_id'], info['name'], 0, Math.max(oldData.length, textValue.length));
|
|
62
|
-
}
|
|
63
|
-
this.insertText(info['collection'], info['document_id'], info['name'], 0, textValue);
|
|
64
|
-
}
|
|
65
|
-
if (info.crud != false) {
|
|
66
|
-
crud.updateDocument({
|
|
67
|
-
collection: info.collection,
|
|
68
|
-
document_id: info.document_id,
|
|
69
|
-
data: {[info.name]: info.value},
|
|
70
|
-
element: info.element,
|
|
71
|
-
metadata:info.metadata,
|
|
72
|
-
namespace: info.namespace,
|
|
73
|
-
room: info.room,
|
|
74
|
-
broadcast: info.broadcast,
|
|
75
|
-
upsert: info.upsert,
|
|
76
|
-
broadcast_sender: info.broadcast_sender
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/*
|
|
82
|
-
crdt.insertText({
|
|
83
|
-
collection: 'module_activities',
|
|
84
|
-
document_id: '5e4802ce3ed96d38e71fc7e5',
|
|
85
|
-
name: 'name',
|
|
86
|
-
value: 'T',
|
|
87
|
-
position: '8',
|
|
88
|
-
attributes: {bold: true}
|
|
89
|
-
})
|
|
90
|
-
*/
|
|
91
|
-
insertText(info) {
|
|
92
|
-
try {
|
|
93
|
-
// this.__validateKeysJson(info,['collection','document_id','name','value','position']);
|
|
94
|
-
let id = this.generateID(config.organization_Id, info['collection'], info['document_id'], info['name'])
|
|
95
|
-
let info1 = this.parseType(id)
|
|
96
|
-
|
|
97
|
-
if (id) {
|
|
98
|
-
this.getType(id).insert(info['position'], info['value'], info['attribute']);
|
|
99
|
-
let wholestring = this.getType(id).toString();
|
|
100
|
-
|
|
101
|
-
console.log(wholestring)
|
|
102
|
-
|
|
103
|
-
if (info.crud != false) {
|
|
104
|
-
crud.updateDocument({
|
|
105
|
-
collection: info.collection,
|
|
106
|
-
document_id: info.document_id,
|
|
107
|
-
data: {
|
|
108
|
-
[info.name]: wholestring
|
|
109
|
-
},
|
|
110
|
-
metadata: 'yjs-change'
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
console.error(e);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
/*
|
|
122
|
-
crdt.deleteText({
|
|
123
|
-
collection: 'module_activities',
|
|
124
|
-
document_id: '5e4802ce3ed96d38e71fc7e5',
|
|
125
|
-
name: 'name',
|
|
126
|
-
position: '8',
|
|
127
|
-
length: 2,
|
|
128
|
-
})
|
|
129
|
-
*/
|
|
130
|
-
|
|
131
|
-
deleteText(info) {
|
|
132
|
-
try{
|
|
133
|
-
// this.__validateKeysJson(info,['collection','document_id','name', 'position','length']);
|
|
134
|
-
let id = this.generateID(config.organization_Id, info['collection'], info['document_id'], info['name'])
|
|
135
|
-
let info1 = this.parseType(id)
|
|
136
|
-
if (id) {
|
|
137
|
-
this.docs[info1.id].doc.getText(id).delete(info['position'], info['length']);
|
|
138
|
-
let wholestring = this.getType(id).toString();
|
|
139
|
-
|
|
140
|
-
console.log(wholestring)
|
|
141
|
-
|
|
142
|
-
if (info.crud != false) {
|
|
143
|
-
crud.updateDocument({
|
|
144
|
-
collection: info.collection,
|
|
145
|
-
document_id: info.document_id,
|
|
146
|
-
data: {
|
|
147
|
-
[info.name]: wholestring
|
|
148
|
-
},
|
|
149
|
-
metadata: 'yjs-change'
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch (e) {
|
|
155
|
-
console.error(e);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
/*
|
|
161
|
-
crdt.getText({
|
|
162
|
-
collection: 'module_activities',
|
|
163
|
-
document_id: '5e4802ce3ed96d38e71fc7e5',
|
|
164
|
-
name: 'name'
|
|
165
|
-
})
|
|
166
|
-
*/
|
|
167
|
-
|
|
168
|
-
getText(info) {
|
|
169
|
-
try{
|
|
170
|
-
// this.__validateKeysJson(info,['collection','document_id','name']);
|
|
171
|
-
let id = this.generateID(config.organization_Id, info['collection'], info['document_id'], info['name'])
|
|
172
|
-
let info1 = this.parseType(id)
|
|
173
|
-
if (id) {
|
|
174
|
-
return this.docs[info1.id].doc.getText(id).toString();
|
|
175
|
-
} else {
|
|
176
|
-
return "--";
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
catch (e) {
|
|
180
|
-
console.error(e);
|
|
181
|
-
return "";
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
/*
|
|
187
|
-
crdt.getPosition(function(data))
|
|
188
|
-
crdt.getPosition(function(data){console.log(" EScuchando ahora ",data)})
|
|
189
|
-
*/
|
|
190
|
-
getPosition(callback){
|
|
191
|
-
if(typeof miFuncion === 'function')
|
|
192
|
-
this.changeListenAwereness(callback);
|
|
193
|
-
else
|
|
194
|
-
console.error('Callback should be a function')
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
__getYDocId(collection, document_id, name) {
|
|
198
|
-
if (!crud.checkAttrValue(collection) ||
|
|
199
|
-
!crud.checkAttrValue(document_id) ||
|
|
200
|
-
!crud.checkAttrValue(name))
|
|
201
|
-
{
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
return this.generateID(config.organization_Id, collection, document_id, name);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
__validateKeysJson(json,rules){
|
|
208
|
-
let keys_json = Object.keys(json);
|
|
209
|
-
keys_json.forEach(key=>{
|
|
210
|
-
const index = rules.indexOf(key);
|
|
211
|
-
if(index != -1)
|
|
212
|
-
rules.splice(index, 1);
|
|
213
|
-
});
|
|
214
|
-
if( rules.length )
|
|
215
|
-
throw "Requires the following "+ rules.toString();
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let CoCreateCrdt = null;
|
|
220
|
-
if (!window.CoCreateCrdt) {
|
|
221
|
-
const crdtDoc = new Y.Doc();
|
|
222
|
-
CoCreateCrdt = new CoCreateCRDTClass(config.organization_Id, crdtDoc);
|
|
223
|
-
window.Y = Y;
|
|
224
|
-
window.CoCreateCrdt = CoCreateCrdt;
|
|
225
|
-
} else {
|
|
226
|
-
CoCreateCrdt = window.CoCreateCrdt;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export default CoCreateCrdt;
|
|
230
|
-
|
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Unlike stated in the LICENSE file, it is not necessary to include the copyright notice and permission notice when you copy code from this file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @module provider/websocket
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/* eslint-env browser */
|
|
10
|
-
|
|
11
|
-
import * as Y from 'yjs' // eslint-disable-line
|
|
12
|
-
import * as bc from 'lib0/broadcastchannel.js'
|
|
13
|
-
import * as time from 'lib0/time.js'
|
|
14
|
-
import * as encoding from 'lib0/encoding.js'
|
|
15
|
-
import * as decoding from 'lib0/decoding.js'
|
|
16
|
-
import * as syncProtocol from 'y-protocols/sync.js'
|
|
17
|
-
import * as authProtocol from 'y-protocols/auth.js'
|
|
18
|
-
import * as awarenessProtocol from 'y-protocols/awareness.js'
|
|
19
|
-
import * as mutex from 'lib0/mutex.js'
|
|
20
|
-
import { Observable } from 'lib0/observable.js'
|
|
21
|
-
import * as math from 'lib0/math.js'
|
|
22
|
-
import * as url from 'lib0/url.js'
|
|
23
|
-
|
|
24
|
-
const messageSync = 0
|
|
25
|
-
const messageQueryAwareness = 3
|
|
26
|
-
const messageAwareness = 1
|
|
27
|
-
const messageAuth = 2
|
|
28
|
-
|
|
29
|
-
const reconnectTimeoutBase = 1200
|
|
30
|
-
const maxReconnectTimeout = 2500
|
|
31
|
-
// @todo - this should depend on awareness.outdatedTime
|
|
32
|
-
const messageReconnectTimeout = 30000
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {WebsocketProvider} provider
|
|
36
|
-
* @param {string} reason
|
|
37
|
-
*/
|
|
38
|
-
const permissionDeniedHandler = (provider, reason) => console.warn(`Permission denied to access ${provider.url}.\n${reason}`)
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param {WebsocketProvider} provider
|
|
42
|
-
* @param {Uint8Array} buf
|
|
43
|
-
* @param {boolean} emitSynced
|
|
44
|
-
* @return {encoding.Encoder}
|
|
45
|
-
*/
|
|
46
|
-
const readMessage = (provider, buf, emitSynced) => {
|
|
47
|
-
const decoder = decoding.createDecoder(buf)
|
|
48
|
-
const encoder = encoding.createEncoder()
|
|
49
|
-
const messageType = decoding.readVarUint(decoder)
|
|
50
|
-
switch (messageType) {
|
|
51
|
-
case messageSync: {
|
|
52
|
-
encoding.writeVarUint(encoder, messageSync)
|
|
53
|
-
const syncMessageType = syncProtocol.readSyncMessage(decoder, encoder, provider.doc, provider)
|
|
54
|
-
if (emitSynced && syncMessageType === syncProtocol.messageYjsSyncStep2 && !provider.synced) {
|
|
55
|
-
provider.synced = true
|
|
56
|
-
}
|
|
57
|
-
break
|
|
58
|
-
}
|
|
59
|
-
case messageQueryAwareness:
|
|
60
|
-
encoding.writeVarUint(encoder, messageAwareness)
|
|
61
|
-
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())))
|
|
62
|
-
break
|
|
63
|
-
case messageAwareness:
|
|
64
|
-
awarenessProtocol.applyAwarenessUpdate(provider.awareness, decoding.readVarUint8Array(decoder), provider)
|
|
65
|
-
break
|
|
66
|
-
case messageAuth:
|
|
67
|
-
authProtocol.readAuthMessage(decoder, provider.doc, permissionDeniedHandler)
|
|
68
|
-
break
|
|
69
|
-
default:
|
|
70
|
-
console.error('Unable to compute message')
|
|
71
|
-
return encoder
|
|
72
|
-
}
|
|
73
|
-
return encoder
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @param {WebsocketProvider} provider
|
|
78
|
-
*/
|
|
79
|
-
const setupWS = provider => {
|
|
80
|
-
if (provider.shouldConnect && provider.ws === null) {
|
|
81
|
-
const websocket = new provider._WS(provider.url)
|
|
82
|
-
websocket.binaryType = 'arraybuffer'
|
|
83
|
-
provider.ws = websocket
|
|
84
|
-
provider.wsconnecting = true
|
|
85
|
-
provider.wsconnected = false
|
|
86
|
-
provider.synced = false
|
|
87
|
-
|
|
88
|
-
websocket.onmessage = event => {
|
|
89
|
-
provider.wsLastMessageReceived = time.getUnixTime()
|
|
90
|
-
const encoder = readMessage(provider, new Uint8Array(event.data), true)
|
|
91
|
-
if (encoding.length(encoder) > 1) {
|
|
92
|
-
websocket.send(encoding.toUint8Array(encoder))
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
websocket.onclose = () => {
|
|
96
|
-
provider.ws = null
|
|
97
|
-
provider.wsconnecting = false
|
|
98
|
-
if (provider.wsconnected) {
|
|
99
|
-
provider.wsconnected = false
|
|
100
|
-
provider.synced = false
|
|
101
|
-
// update awareness (all users except local left)
|
|
102
|
-
awarenessProtocol.removeAwarenessStates(provider.awareness, Array.from(provider.awareness.getStates().keys()).filter(client => client !== provider.doc.clientID), provider)
|
|
103
|
-
provider.emit('status', [{
|
|
104
|
-
status: 'disconnected'
|
|
105
|
-
}])
|
|
106
|
-
} else {
|
|
107
|
-
provider.wsUnsuccessfulReconnects++
|
|
108
|
-
}
|
|
109
|
-
// Start with no reconnect timeout and increase timeout by
|
|
110
|
-
// log10(wsUnsuccessfulReconnects).
|
|
111
|
-
// The idea is to increase reconnect timeout slowly and have no reconnect
|
|
112
|
-
// timeout at the beginning (log(1) = 0)
|
|
113
|
-
setTimeout(setupWS, math.min(math.log10(provider.wsUnsuccessfulReconnects + 1) * reconnectTimeoutBase, maxReconnectTimeout), provider)
|
|
114
|
-
}
|
|
115
|
-
websocket.onopen = () => {
|
|
116
|
-
provider.wsLastMessageReceived = time.getUnixTime()
|
|
117
|
-
provider.wsconnecting = false
|
|
118
|
-
provider.wsconnected = true
|
|
119
|
-
provider.wsUnsuccessfulReconnects = 0
|
|
120
|
-
provider.emit('status', [{
|
|
121
|
-
status: 'connected'
|
|
122
|
-
}])
|
|
123
|
-
// always send sync step 1 when connected
|
|
124
|
-
const encoder = encoding.createEncoder()
|
|
125
|
-
encoding.writeVarUint(encoder, messageSync)
|
|
126
|
-
syncProtocol.writeSyncStep1(encoder, provider.doc)
|
|
127
|
-
websocket.send(encoding.toUint8Array(encoder))
|
|
128
|
-
// broadcast local awareness state
|
|
129
|
-
if (provider.awareness.getLocalState() !== null) {
|
|
130
|
-
const encoderAwarenessState = encoding.createEncoder()
|
|
131
|
-
encoding.writeVarUint(encoderAwarenessState, messageAwareness)
|
|
132
|
-
encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(provider.awareness, [provider.doc.clientID]))
|
|
133
|
-
websocket.send(encoding.toUint8Array(encoderAwarenessState))
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
provider.emit('status', [{
|
|
138
|
-
status: 'connecting'
|
|
139
|
-
}])
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* @param {WebsocketProvider} provider
|
|
145
|
-
* @param {ArrayBuffer} buf
|
|
146
|
-
*/
|
|
147
|
-
const broadcastMessage = (provider, buf) => {
|
|
148
|
-
if (provider.wsconnected) {
|
|
149
|
-
// @ts-ignore We know that wsconnected = true
|
|
150
|
-
provider.ws.send(buf)
|
|
151
|
-
}
|
|
152
|
-
if (provider.bcconnected) {
|
|
153
|
-
provider.mux(() => {
|
|
154
|
-
bc.publish(provider.bcChannel, buf)
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Websocket Provider for Yjs. Creates a websocket connection to sync the shared document.
|
|
161
|
-
* The document name is attached to the provided url. I.e. the following example
|
|
162
|
-
* creates a websocket connection to http://localhost:1234/my-document-name
|
|
163
|
-
*
|
|
164
|
-
* @example
|
|
165
|
-
* import * as Y from 'yjs'
|
|
166
|
-
* import { WebsocketProvider } from 'y-websocket'
|
|
167
|
-
* const doc = new Y.Doc()
|
|
168
|
-
* const provider = new WebsocketProvider('http://localhost:1234', 'my-document-name', doc)
|
|
169
|
-
*
|
|
170
|
-
* @extends {Observable<string>}
|
|
171
|
-
*/
|
|
172
|
-
export class WebsocketProvider extends Observable {
|
|
173
|
-
/**
|
|
174
|
-
* @param {string} serverUrl
|
|
175
|
-
* @param {string} roomname
|
|
176
|
-
* @param {Y.Doc} doc
|
|
177
|
-
* @param {object} [opts]
|
|
178
|
-
* @param {boolean} [opts.connect]
|
|
179
|
-
* @param {awarenessProtocol.Awareness} [opts.awareness]
|
|
180
|
-
* @param {Object<string,string>} [opts.params]
|
|
181
|
-
* @param {typeof WebSocket} [opts.WebSocketPolyfill] Optionall provide a WebSocket polyfill
|
|
182
|
-
* @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds
|
|
183
|
-
*/
|
|
184
|
-
constructor (serverUrl, roomname, doc, { connect = true, awareness = new awarenessProtocol.Awareness(doc), params = {}, WebSocketPolyfill = WebSocket, resyncInterval = -1 } = {}) {
|
|
185
|
-
super()
|
|
186
|
-
// ensure that url is always ends with /
|
|
187
|
-
while (serverUrl[serverUrl.length - 1] === '/') {
|
|
188
|
-
serverUrl = serverUrl.slice(0, serverUrl.length - 1)
|
|
189
|
-
}
|
|
190
|
-
const encodedParams = url.encodeQueryParams(params)
|
|
191
|
-
this.bcChannel = serverUrl + '/' + roomname
|
|
192
|
-
this.url = serverUrl + '/' + roomname + (encodedParams.length === 0 ? '' : '?' + encodedParams)
|
|
193
|
-
this.roomname = roomname
|
|
194
|
-
this.doc = doc
|
|
195
|
-
this._WS = WebSocketPolyfill
|
|
196
|
-
this.awareness = awareness
|
|
197
|
-
this.wsconnected = false
|
|
198
|
-
this.wsconnecting = false
|
|
199
|
-
this.bcconnected = false
|
|
200
|
-
this.wsUnsuccessfulReconnects = 0
|
|
201
|
-
this.mux = mutex.createMutex()
|
|
202
|
-
/**
|
|
203
|
-
* @type {boolean}
|
|
204
|
-
*/
|
|
205
|
-
this._synced = false
|
|
206
|
-
/**
|
|
207
|
-
* @type {WebSocket?}
|
|
208
|
-
*/
|
|
209
|
-
this.ws = null
|
|
210
|
-
this.wsLastMessageReceived = 0
|
|
211
|
-
/**
|
|
212
|
-
* Whether to connect to other peers or not
|
|
213
|
-
* @type {boolean}
|
|
214
|
-
*/
|
|
215
|
-
this.shouldConnect = connect
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* @type {NodeJS.Timeout | number}
|
|
219
|
-
*/
|
|
220
|
-
this._resyncInterval = 0
|
|
221
|
-
if (resyncInterval > 0) {
|
|
222
|
-
this._resyncInterval = setInterval(() => {
|
|
223
|
-
if (this.ws) {
|
|
224
|
-
// resend sync step 1
|
|
225
|
-
const encoder = encoding.createEncoder()
|
|
226
|
-
encoding.writeVarUint(encoder, messageSync)
|
|
227
|
-
syncProtocol.writeSyncStep1(encoder, doc)
|
|
228
|
-
this.ws.send(encoding.toUint8Array(encoder))
|
|
229
|
-
}
|
|
230
|
-
}, resyncInterval)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* @param {ArrayBuffer} data
|
|
235
|
-
*/
|
|
236
|
-
this._bcSubscriber = data => {
|
|
237
|
-
this.mux(() => {
|
|
238
|
-
const encoder = readMessage(this, new Uint8Array(data), false)
|
|
239
|
-
if (encoding.length(encoder) > 1) {
|
|
240
|
-
bc.publish(this.bcChannel, encoding.toUint8Array(encoder))
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Listens to Yjs updates and sends them to remote peers (ws and broadcastchannel)
|
|
246
|
-
* @param {Uint8Array} update
|
|
247
|
-
* @param {any} origin
|
|
248
|
-
*/
|
|
249
|
-
this._updateHandler = (update, origin) => {
|
|
250
|
-
if (origin !== this || origin === null) {
|
|
251
|
-
const encoder = encoding.createEncoder()
|
|
252
|
-
encoding.writeVarUint(encoder, messageSync)
|
|
253
|
-
syncProtocol.writeUpdate(encoder, update)
|
|
254
|
-
broadcastMessage(this, encoding.toUint8Array(encoder))
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
this.doc.on('update', this._updateHandler)
|
|
258
|
-
/**
|
|
259
|
-
* @param {any} changed
|
|
260
|
-
* @param {any} origin
|
|
261
|
-
*/
|
|
262
|
-
this._awarenessUpdateHandler = ({ added, updated, removed }, origin) => {
|
|
263
|
-
const changedClients = added.concat(updated).concat(removed)
|
|
264
|
-
const encoder = encoding.createEncoder()
|
|
265
|
-
encoding.writeVarUint(encoder, messageAwareness)
|
|
266
|
-
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(awareness, changedClients))
|
|
267
|
-
broadcastMessage(this, encoding.toUint8Array(encoder))
|
|
268
|
-
}
|
|
269
|
-
window.addEventListener('beforeunload', () => {
|
|
270
|
-
awarenessProtocol.removeAwarenessStates(this.awareness, [doc.clientID], 'window unload')
|
|
271
|
-
})
|
|
272
|
-
awareness.on('update', this._awarenessUpdateHandler)
|
|
273
|
-
this._checkInterval = setInterval(() => {
|
|
274
|
-
if (this.wsconnected && messageReconnectTimeout < time.getUnixTime() - this.wsLastMessageReceived) {
|
|
275
|
-
// no message received in a long time - not even your own awareness
|
|
276
|
-
// updates (which are updated every 15 seconds)
|
|
277
|
-
/** @type {WebSocket} */ (this.ws).close()
|
|
278
|
-
}
|
|
279
|
-
}, messageReconnectTimeout / 10)
|
|
280
|
-
if (connect) {
|
|
281
|
-
this.connect()
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* @type {boolean}
|
|
287
|
-
*/
|
|
288
|
-
get synced () {
|
|
289
|
-
return this._synced
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
set synced (state) {
|
|
293
|
-
if (this._synced !== state) {
|
|
294
|
-
this._synced = state
|
|
295
|
-
this.emit('synced', [state])
|
|
296
|
-
this.emit('sync', [state])
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
destroy () {
|
|
301
|
-
if (this._resyncInterval !== 0) {
|
|
302
|
-
clearInterval(/** @type {NodeJS.Timeout} */ (this._resyncInterval))
|
|
303
|
-
}
|
|
304
|
-
clearInterval(this._checkInterval)
|
|
305
|
-
this.disconnect()
|
|
306
|
-
this.awareness.off('update', this._awarenessUpdateHandler)
|
|
307
|
-
this.doc.off('update', this._updateHandler)
|
|
308
|
-
super.destroy()
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
connectBc () {
|
|
312
|
-
if (!this.bcconnected) {
|
|
313
|
-
bc.subscribe(this.bcChannel, this._bcSubscriber)
|
|
314
|
-
this.bcconnected = true
|
|
315
|
-
}
|
|
316
|
-
// send sync step1 to bc
|
|
317
|
-
this.mux(() => {
|
|
318
|
-
// write sync step 1
|
|
319
|
-
const encoderSync = encoding.createEncoder()
|
|
320
|
-
encoding.writeVarUint(encoderSync, messageSync)
|
|
321
|
-
syncProtocol.writeSyncStep1(encoderSync, this.doc)
|
|
322
|
-
bc.publish(this.bcChannel, encoding.toUint8Array(encoderSync))
|
|
323
|
-
// broadcast local state
|
|
324
|
-
const encoderState = encoding.createEncoder()
|
|
325
|
-
encoding.writeVarUint(encoderState, messageSync)
|
|
326
|
-
syncProtocol.writeSyncStep2(encoderState, this.doc)
|
|
327
|
-
bc.publish(this.bcChannel, encoding.toUint8Array(encoderState))
|
|
328
|
-
// write queryAwareness
|
|
329
|
-
const encoderAwarenessQuery = encoding.createEncoder()
|
|
330
|
-
encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness)
|
|
331
|
-
bc.publish(this.bcChannel, encoding.toUint8Array(encoderAwarenessQuery))
|
|
332
|
-
// broadcast local awareness state
|
|
333
|
-
const encoderAwarenessState = encoding.createEncoder()
|
|
334
|
-
encoding.writeVarUint(encoderAwarenessState, messageAwareness)
|
|
335
|
-
encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this.doc.clientID]))
|
|
336
|
-
bc.publish(this.bcChannel, encoding.toUint8Array(encoderAwarenessState))
|
|
337
|
-
})
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
disconnectBc () {
|
|
341
|
-
// broadcast message with local awareness state set to null (indicating disconnect)
|
|
342
|
-
const encoder = encoding.createEncoder()
|
|
343
|
-
encoding.writeVarUint(encoder, messageAwareness)
|
|
344
|
-
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this.doc.clientID], new Map()))
|
|
345
|
-
broadcastMessage(this, encoding.toUint8Array(encoder))
|
|
346
|
-
if (this.bcconnected) {
|
|
347
|
-
bc.unsubscribe(this.bcChannel, this._bcSubscriber)
|
|
348
|
-
this.bcconnected = false
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
disconnect () {
|
|
353
|
-
this.shouldConnect = false
|
|
354
|
-
this.disconnectBc()
|
|
355
|
-
if (this.ws !== null) {
|
|
356
|
-
this.ws.close()
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
connect () {
|
|
361
|
-
this.shouldConnect = true
|
|
362
|
-
if (!this.wsconnected && this.ws === null) {
|
|
363
|
-
setupWS(this)
|
|
364
|
-
this.connectBc()
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|