@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 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.
@@ -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
+ }