-
Notifications
You must be signed in to change notification settings - Fork 0
/
WebxdcSyncProviderGeneric.js
136 lines (130 loc) · 4.64 KB
/
WebxdcSyncProviderGeneric.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/** @license Apache-2.0 */
// @ts-check
import { applyUpdateV2 as YApplyUpdate, mergeUpdatesV2 as YMergeUpdates } from "yjs"
/** @typedef {import("yjs").Doc} YDoc */
/** @typedef {Parameters<YApplyUpdate>[2]} TransactionOrigin */
/** @typedef {Parameters<YApplyUpdate>[1]} YUpdate */
// TODO add a way to sync several documents (i.e. call this more than once, for
// different documents), or document that only one doc per context is supported.
export class WebxdcSyncProvider {
/**
* @param {YDoc} ydoc
* @param {TransactionOrigin} transactionOrigin `transactionOrigin` to use when applying updates
* coming from `webxdc.setUpdateListener` to the {@link ydoc}
* (see {@link onIncomingYjsUpdate}, {@link YApplyUpdate|`Y.applyUpdate`})
* @param {(update: YUpdate) => unknown} serializeUpdate
* [The webxdc spec requires](https://github.com/webxdc/webxdc_docs/blob/18f5e5a7bb62bdd9df47b129179948aac269769b/src/spec.md#sendupdate)
* update payloads to be
*
* > any javascript primitive, array or object
*
* Yjs updates are `Uint8Array`, which are not primitives, so we need to transform them.
* https://docs.yjs.dev/api/document-updates#example-base64-encoding
* @param {(serializedUpdate: unknown) => YUpdate} deserializeUpdate
* see {@link serializeUpdate}
* @param {(
* outgoingSerializedYjsUpdate: ReturnType<WebxdcSyncProvider['_serializeUpdate']>
* ) => void} sendUpdate
*/
constructor(
ydoc,
serializeUpdate,
deserializeUpdate,
sendUpdate,
transactionOrigin = '__webxdcUpdateHandler'
) {
// TODO refactor: import webxdc types.
// https://github.com/webxdc/webxdc_docs/blob/18f5e5a7bb62bdd9df47b129179948aac269769b/src/tips_and_tricks.md#get-the-typescript-definitions
/**
* This must be called for every update that hasn't been applied to
* the {@link ydoc}. This includes updates sent by other peers, and updates
* from the previous sessions. Calling it with an update that has already
* been applied is OK, and it does nothing.
* @public
* @readonly
* @param {Parameters<typeof deserializeUpdate>[0]} incomingSerializedYjsUpdate
* @returns {void}
*/
this.onIncomingYjsUpdate = function (incomingSerializedYjsUpdate) {
// TODO maybe `transactionOrigin` should be the `selfAddr` of the sender??
// Perhaps with `webxdc` (pre|suf)fix.
YApplyUpdate(ydoc, deserializeUpdate(incomingSerializedYjsUpdate), transactionOrigin);
}
/**
* @private
* @type {typeof sendUpdate}
*/
this.sendUpdate = sendUpdate;
/**
* @private
* @type {YUpdate[]}
*/
this._unsentLocalUpdates = [];
/**
* @private
*/
this._serializeUpdate = serializeUpdate;
// TODO Consider getting rid of the default value, because it's not obvious
// to users that webxdc updates are sent on each Yjs udpate.
/**
* This function is automatically called whenever the {@link ydoc} is
* updated by us (and not the other peers), that is whenever we have updates
* that need to be sent to other peers.
*
* You can override this property.
*
* The default value is {@link sendUnsentLocalUpdates}, i.e. each update gets sent immediately.
*
* @public
* @type {() => void}
*/
this.onNeedToSendLocalUpdates = this.sendUnsentLocalUpdates;
ydoc.on('updateV2', (/** @type {Uint8Array} */update, origin) => {
if (origin === transactionOrigin) {
return
}
this._unsentLocalUpdates.push(update);
this.onNeedToSendLocalUpdates();
});
}
/**
* Send the unsent local updates to other peers.
* Also see {@link onNeedToSendLocalUpdates}
* @public
* @returns {void}
*/
sendUnsentLocalUpdates() {
if (this._unsentLocalUpdates.length <= 0) {
return;
}
this.sendUpdate(this.takeOutUnsentLocalUpdates());
}
// TODO refactor: if the library user wants to use only this function to send
// updates and never `sendUnsentLocalUpdates` then requiring
// `sendUpdate` as a constructor parameter doesn't make sense.
/**
* Useful when you want to send the Yjs updates data manually.
* Returns `undefined` if there are no unsent updates.
* @see {@link sendUnsentLocalUpdates}
* @public
* @returns {ReturnType<WebxdcSyncProvider['_serializeUpdate']> | undefined}
*/
takeOutUnsentLocalUpdates() {
if (this._unsentLocalUpdates.length <= 0) {
return undefined;
}
const ret = this._serializeUpdate(YMergeUpdates(this._unsentLocalUpdates));
// TODO refactor: idk, maybe we need to rename things, because the fact
// that we "took out" unsent local updates doesn't necessarily mean that
// we're gonna send them.
this._unsentLocalUpdates = [];
return ret;
}
/**
* @public
* @returns {boolean}
*/
get haveUnsentUpdates() {
return this._unsentLocalUpdates.length > 0;
}
}