@liveblocks/yjs 1.0.12-yjs1
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 +46 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +267 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://liveblocks.io#gh-light-mode-only">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-light.svg" alt="Liveblocks" />
|
|
4
|
+
</a>
|
|
5
|
+
<a href="https://liveblocks.io#gh-dark-mode-only">
|
|
6
|
+
<img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-dark.svg" alt="Liveblocks" />
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
# `@liveblocks/yjs`
|
|
11
|
+
|
|
12
|
+
Provides YJS integration to effortlessly back your YJS apps with Liveblocks
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npm install @liveblocks/client @liveblocks/yjs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
Read the
|
|
23
|
+
[documentation](https://liveblocks.io/docs/api-reference/liveblocks-yjs) for
|
|
24
|
+
guides and API references.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
## Releases
|
|
29
|
+
|
|
30
|
+
See the [latest changes](https://github.com/liveblocks/liveblocks/releases) or
|
|
31
|
+
learn more about
|
|
32
|
+
[upcoming releases](https://github.com/liveblocks/liveblocks/milestones).
|
|
33
|
+
|
|
34
|
+
## Community
|
|
35
|
+
|
|
36
|
+
- [Discord](https://liveblocks.io/discord) - To get involved with the Liveblocks
|
|
37
|
+
community, ask questions and share tips.
|
|
38
|
+
- [Twitter](https://twitter.com/liveblocks) - To receive updates, announcements,
|
|
39
|
+
blog posts, and general Liveblocks tips.
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
Licensed under the Apache License 2.0, Copyright © 2021-present
|
|
44
|
+
[Liveblocks](https://liveblocks.io).
|
|
45
|
+
|
|
46
|
+
See [LICENSE](../../LICENSE) for more information.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Room, JsonObject, LsonObject, BaseUserMeta, Json } from '@liveblocks/client';
|
|
2
|
+
import { Observable } from 'lib0/observable';
|
|
3
|
+
import * as Y from 'yjs';
|
|
4
|
+
|
|
5
|
+
declare type LiveblocksYjsOptions = {
|
|
6
|
+
httpEndpoint?: string;
|
|
7
|
+
};
|
|
8
|
+
declare type MetaClientState = {
|
|
9
|
+
clock: number;
|
|
10
|
+
lastUpdated: number;
|
|
11
|
+
};
|
|
12
|
+
declare class Awareness extends Observable<any> {
|
|
13
|
+
private room;
|
|
14
|
+
doc: Y.Doc;
|
|
15
|
+
clientID: number;
|
|
16
|
+
states: Map<number, any>;
|
|
17
|
+
meta: Map<number, MetaClientState>;
|
|
18
|
+
_checkInterval: number;
|
|
19
|
+
private othersUnsub;
|
|
20
|
+
constructor(doc: Y.Doc, room: Room<JsonObject, LsonObject, BaseUserMeta, Json>);
|
|
21
|
+
destroy(): void;
|
|
22
|
+
getLocalState(): JsonObject | null;
|
|
23
|
+
setLocalState(state: Partial<JsonObject> | null): void;
|
|
24
|
+
setLocalStateField(field: string, value: JsonObject | null): void;
|
|
25
|
+
getStates(): Map<number, any>;
|
|
26
|
+
}
|
|
27
|
+
declare class LiveblocksProvider<P extends JsonObject, S extends LsonObject, U extends BaseUserMeta, E extends Json> {
|
|
28
|
+
private room;
|
|
29
|
+
private httpEndpoint?;
|
|
30
|
+
private lastUpdateDate;
|
|
31
|
+
private doc;
|
|
32
|
+
private unsubscribers;
|
|
33
|
+
awareness: Awareness;
|
|
34
|
+
constructor(room: Room<P, S, U, E>, doc: Y.Doc, config?: LiveblocksYjsOptions);
|
|
35
|
+
private updateHandler;
|
|
36
|
+
private resyncHttp;
|
|
37
|
+
destroy(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Awareness, LiveblocksProvider as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
var __async = (__this, __arguments, generator) => {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
var fulfilled = (value) => {
|
|
20
|
+
try {
|
|
21
|
+
step(generator.next(value));
|
|
22
|
+
} catch (e) {
|
|
23
|
+
reject(e);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var rejected = (value) => {
|
|
27
|
+
try {
|
|
28
|
+
step(generator.throw(value));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
reject(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
34
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/index.ts
|
|
39
|
+
var _jsbase64 = require('js-base64');
|
|
40
|
+
|
|
41
|
+
// ../../node_modules/lib0/map.js
|
|
42
|
+
var create = () => /* @__PURE__ */ new Map();
|
|
43
|
+
var setIfUndefined = (map, key, createT) => {
|
|
44
|
+
let set = map.get(key);
|
|
45
|
+
if (set === void 0) {
|
|
46
|
+
map.set(key, set = createT());
|
|
47
|
+
}
|
|
48
|
+
return set;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ../../node_modules/lib0/set.js
|
|
52
|
+
var create2 = () => /* @__PURE__ */ new Set();
|
|
53
|
+
|
|
54
|
+
// ../../node_modules/lib0/array.js
|
|
55
|
+
var from = Array.from;
|
|
56
|
+
var isArray = Array.isArray;
|
|
57
|
+
|
|
58
|
+
// ../../node_modules/lib0/observable.js
|
|
59
|
+
var Observable = class {
|
|
60
|
+
constructor() {
|
|
61
|
+
this._observers = create();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @param {N} name
|
|
65
|
+
* @param {function} f
|
|
66
|
+
*/
|
|
67
|
+
on(name, f) {
|
|
68
|
+
setIfUndefined(this._observers, name, create2).add(f);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @param {N} name
|
|
72
|
+
* @param {function} f
|
|
73
|
+
*/
|
|
74
|
+
once(name, f) {
|
|
75
|
+
const _f = (...args) => {
|
|
76
|
+
this.off(name, _f);
|
|
77
|
+
f(...args);
|
|
78
|
+
};
|
|
79
|
+
this.on(name, _f);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @param {N} name
|
|
83
|
+
* @param {function} f
|
|
84
|
+
*/
|
|
85
|
+
off(name, f) {
|
|
86
|
+
const observers = this._observers.get(name);
|
|
87
|
+
if (observers !== void 0) {
|
|
88
|
+
observers.delete(f);
|
|
89
|
+
if (observers.size === 0) {
|
|
90
|
+
this._observers.delete(name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Emit a named event. All registered event listeners that listen to the
|
|
96
|
+
* specified name will receive the event.
|
|
97
|
+
*
|
|
98
|
+
* @todo This should catch exceptions
|
|
99
|
+
*
|
|
100
|
+
* @param {N} name The event name.
|
|
101
|
+
* @param {Array<any>} args The arguments that are applied to the event listener.
|
|
102
|
+
*/
|
|
103
|
+
emit(name, args) {
|
|
104
|
+
return from((this._observers.get(name) || create()).values()).forEach((f) => f(...args));
|
|
105
|
+
}
|
|
106
|
+
destroy() {
|
|
107
|
+
this._observers = create();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/index.ts
|
|
112
|
+
var _yjs = require('yjs'); var Y = _interopRequireWildcard(_yjs);
|
|
113
|
+
var Awareness = class extends Observable {
|
|
114
|
+
constructor(doc, room) {
|
|
115
|
+
super();
|
|
116
|
+
this.states = /* @__PURE__ */ new Map();
|
|
117
|
+
// Meta is used to keep track and timeout users who disconnect. Liveblocks provides this for us, so we don't need to
|
|
118
|
+
// manage it here. Unfortunately, it's expected to exist by various integrations, so it's an empty map.
|
|
119
|
+
this.meta = /* @__PURE__ */ new Map();
|
|
120
|
+
// _checkInterval this would hold a timer to remove users, but Liveblock's presence already handles this
|
|
121
|
+
// unfortunately it's expected to exist by various integrations.
|
|
122
|
+
this._checkInterval = 0;
|
|
123
|
+
this.doc = doc;
|
|
124
|
+
this.room = room;
|
|
125
|
+
this.clientID = doc.clientID;
|
|
126
|
+
this.othersUnsub = this.room.events.others.subscribe(({ event }) => {
|
|
127
|
+
if (event.type === "leave") {
|
|
128
|
+
this.emit("change", [
|
|
129
|
+
{ added: [], updated: [], removed: [event.user.connectionId] },
|
|
130
|
+
"local"
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
if (event.type === "enter") {
|
|
134
|
+
this.emit("change", [
|
|
135
|
+
{ added: [event.user.connectionId], updated: [], removed: [] },
|
|
136
|
+
"local"
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
if (event.type === "update") {
|
|
140
|
+
this.emit("change", [
|
|
141
|
+
{ added: [], updated: [event.user.connectionId], removed: [] },
|
|
142
|
+
"local"
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
destroy() {
|
|
148
|
+
this.emit("destroy", [this]);
|
|
149
|
+
this.othersUnsub();
|
|
150
|
+
this.setLocalState(null);
|
|
151
|
+
super.destroy();
|
|
152
|
+
}
|
|
153
|
+
getLocalState() {
|
|
154
|
+
const presence = this.room.getPresence();
|
|
155
|
+
if (Object.keys(this.room.getPresence()).length === 0) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return presence["__yjs"];
|
|
159
|
+
}
|
|
160
|
+
setLocalState(state) {
|
|
161
|
+
const self = this.room.getSelf();
|
|
162
|
+
if (!self) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.room.updatePresence({ __yjs: __spreadValues({}, state || {}) });
|
|
166
|
+
}
|
|
167
|
+
setLocalStateField(field, value) {
|
|
168
|
+
const update = { [field]: value };
|
|
169
|
+
this.room.updatePresence({ __yjs: update });
|
|
170
|
+
}
|
|
171
|
+
// Translate
|
|
172
|
+
getStates() {
|
|
173
|
+
const others = this.room.getOthers();
|
|
174
|
+
const states = others.reduce((acc, currentValue) => {
|
|
175
|
+
if (currentValue.connectionId) {
|
|
176
|
+
acc.set(currentValue.connectionId, currentValue.presence["__yjs"]);
|
|
177
|
+
}
|
|
178
|
+
return acc;
|
|
179
|
+
}, /* @__PURE__ */ new Map());
|
|
180
|
+
return states;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var LiveblocksProvider = class {
|
|
184
|
+
constructor(room, doc, config) {
|
|
185
|
+
this.lastUpdateDate = null;
|
|
186
|
+
this.unsubscribers = [];
|
|
187
|
+
this.updateHandler = (update, origin) => __async(this, null, function* () {
|
|
188
|
+
if (origin !== "backend") {
|
|
189
|
+
const encodedUpdate = _jsbase64.Base64.fromUint8Array(update);
|
|
190
|
+
this.room.updateDoc(encodedUpdate);
|
|
191
|
+
if (this.httpEndpoint) {
|
|
192
|
+
yield fetch(this.httpEndpoint, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
body: encodedUpdate
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
var _a;
|
|
200
|
+
this.doc = doc;
|
|
201
|
+
this.room = room;
|
|
202
|
+
const connectionId = (_a = this.room.getSelf()) == null ? void 0 : _a.connectionId;
|
|
203
|
+
if (connectionId) {
|
|
204
|
+
this.doc.clientID = connectionId;
|
|
205
|
+
}
|
|
206
|
+
this.awareness = new Awareness(this.doc, this.room);
|
|
207
|
+
this.doc.on("update", this.updateHandler);
|
|
208
|
+
this.unsubscribers.push(
|
|
209
|
+
this.room.events.connection.subscribe((e) => {
|
|
210
|
+
var _a2;
|
|
211
|
+
if (e === "open") {
|
|
212
|
+
this.doc.clientID = ((_a2 = this.room.getSelf()) == null ? void 0 : _a2.connectionId) || this.doc.clientID;
|
|
213
|
+
this.awareness.clientID = this.doc.clientID;
|
|
214
|
+
const encodedVector = _jsbase64.Base64.fromUint8Array(
|
|
215
|
+
Y.encodeStateVector(this.doc)
|
|
216
|
+
);
|
|
217
|
+
this.room.getDoc(encodedVector);
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
this.unsubscribers.push(
|
|
222
|
+
this.room.events.docUpdated.subscribe((updates) => {
|
|
223
|
+
const decodedUpdates = updates.map(_jsbase64.Base64.toUint8Array);
|
|
224
|
+
const update = Y.mergeUpdates(decodedUpdates);
|
|
225
|
+
Y.applyUpdate(this.doc, update, "backend");
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
if (config == null ? void 0 : config.httpEndpoint) {
|
|
229
|
+
this.httpEndpoint = config.httpEndpoint + "?room=" + this.room.id;
|
|
230
|
+
this.unsubscribers.push(
|
|
231
|
+
this.room.events.customEvent.subscribe(({ event }) => {
|
|
232
|
+
if ((event == null ? void 0 : event.type) === "REFRESH") {
|
|
233
|
+
void this.resyncHttp();
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
void this.resyncHttp();
|
|
238
|
+
}
|
|
239
|
+
this.room.getDoc();
|
|
240
|
+
}
|
|
241
|
+
resyncHttp() {
|
|
242
|
+
return __async(this, null, function* () {
|
|
243
|
+
if (!this.httpEndpoint) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const response = yield fetch(
|
|
247
|
+
`${this.httpEndpoint}${this.lastUpdateDate !== null ? `&after=${this.lastUpdateDate.toISOString()}` : ""}`
|
|
248
|
+
);
|
|
249
|
+
const { updates, lastUpdate } = yield response.json();
|
|
250
|
+
if (updates.length === 0) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.lastUpdateDate = new Date(lastUpdate);
|
|
254
|
+
const update = Y.mergeUpdates(updates.map(_jsbase64.Base64.toUint8Array));
|
|
255
|
+
Y.applyUpdate(this.doc, update, "backend");
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
destroy() {
|
|
259
|
+
this.doc.off("update", this.updateHandler);
|
|
260
|
+
this.unsubscribers.forEach((unsub) => unsub());
|
|
261
|
+
this.awareness.destroy();
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
exports.Awareness = Awareness; exports.default = LiveblocksProvider;
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liveblocks/yjs",
|
|
3
|
+
"version": "1.0.12-yjs1",
|
|
4
|
+
"description": "An integration with . Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"format": "eslint --fix src/; prettier --write src/",
|
|
16
|
+
"lint": "eslint src/",
|
|
17
|
+
"test": "jest --silent --verbose --color=always",
|
|
18
|
+
"test:types": "tsd",
|
|
19
|
+
"test:watch": "jest --silent --verbose --color=always --watch"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@liveblocks/client": "1.0.12-yjs1",
|
|
23
|
+
"@liveblocks/core": "1.0.12-yjs1",
|
|
24
|
+
"js-base64": "^3.7.5"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"yjs": "^13.6.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@liveblocks/eslint-config": "*",
|
|
31
|
+
"@liveblocks/jest-config": "*",
|
|
32
|
+
"@testing-library/jest-dom": "^5.16.5"
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/liveblocks/liveblocks/issues"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/liveblocks/liveblocks.git",
|
|
41
|
+
"directory": "packages/liveblocks-yjs"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://liveblocks.io",
|
|
44
|
+
"keywords": [
|
|
45
|
+
"yjs",
|
|
46
|
+
"react",
|
|
47
|
+
"liveblocks",
|
|
48
|
+
"real-time",
|
|
49
|
+
"toolkit",
|
|
50
|
+
"multiplayer",
|
|
51
|
+
"websockets",
|
|
52
|
+
"collaboration",
|
|
53
|
+
"collaborative",
|
|
54
|
+
"presence",
|
|
55
|
+
"crdts",
|
|
56
|
+
"synchronize",
|
|
57
|
+
"rooms",
|
|
58
|
+
"documents",
|
|
59
|
+
"conflict resolution"
|
|
60
|
+
]
|
|
61
|
+
}
|