@symbo.ls/sdk 3.1.2 → 3.2.3
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 +2 -2
- package/dist/cjs/config/environment.js +5 -21
- package/dist/cjs/index.js +6 -26
- package/dist/cjs/services/AIService.js +3 -3
- package/dist/cjs/services/CollabService.js +420 -0
- package/dist/cjs/services/CoreService.js +651 -107
- package/dist/cjs/services/SocketService.js +207 -59
- package/dist/cjs/services/index.js +5 -13
- package/dist/cjs/state/RootStateManager.js +86 -0
- package/dist/cjs/state/rootEventBus.js +65 -0
- package/dist/cjs/utils/CollabClient.js +157 -0
- package/dist/cjs/utils/TokenManager.js +62 -27
- package/dist/cjs/utils/jsonDiff.js +103 -0
- package/dist/cjs/utils/services.js +129 -88
- package/dist/cjs/utils/symstoryClient.js +5 -5
- package/dist/esm/config/environment.js +5 -21
- package/dist/esm/index.js +20459 -9286
- package/dist/esm/services/AIService.js +3 -3
- package/dist/esm/services/BasedService.js +5 -21
- package/dist/esm/services/CollabService.js +18028 -0
- package/dist/esm/services/CoreService.js +718 -155
- package/dist/esm/services/SocketService.js +323 -58
- package/dist/esm/services/SymstoryService.js +10 -26
- package/dist/esm/services/index.js +20305 -9158
- package/dist/esm/state/RootStateManager.js +102 -0
- package/dist/esm/state/rootEventBus.js +47 -0
- package/dist/esm/utils/CollabClient.js +17483 -0
- package/dist/esm/utils/TokenManager.js +62 -27
- package/dist/esm/utils/jsonDiff.js +6096 -0
- package/dist/esm/utils/services.js +129 -88
- package/dist/esm/utils/symstoryClient.js +10 -26
- package/dist/node/config/environment.js +5 -21
- package/dist/node/index.js +10 -34
- package/dist/node/services/AIService.js +3 -3
- package/dist/node/services/CollabService.js +401 -0
- package/dist/node/services/CoreService.js +651 -107
- package/dist/node/services/SocketService.js +197 -59
- package/dist/node/services/index.js +5 -13
- package/dist/node/state/RootStateManager.js +57 -0
- package/dist/node/state/rootEventBus.js +46 -0
- package/dist/node/utils/CollabClient.js +128 -0
- package/dist/node/utils/TokenManager.js +62 -27
- package/dist/node/utils/jsonDiff.js +74 -0
- package/dist/node/utils/services.js +129 -88
- package/dist/node/utils/symstoryClient.js +5 -5
- package/package.json +12 -6
- package/src/config/environment.js +5 -19
- package/src/index.js +9 -31
- package/src/services/AIService.js +3 -3
- package/src/services/BasedService.js +1 -0
- package/src/services/CollabService.js +491 -0
- package/src/services/CoreService.js +715 -110
- package/src/services/SocketService.js +227 -59
- package/src/services/index.js +6 -13
- package/src/state/RootStateManager.js +71 -0
- package/src/state/rootEventBus.js +48 -0
- package/src/utils/CollabClient.js +161 -0
- package/src/utils/TokenManager.js +68 -30
- package/src/utils/jsonDiff.js +109 -0
- package/src/utils/services.js +140 -88
- package/src/utils/symstoryClient.js +5 -5
- package/dist/cjs/services/SocketIOService.js +0 -307
- package/dist/esm/services/SocketIOService.js +0 -470
- package/dist/node/services/SocketIOService.js +0 -278
- package/src/services/SocketIOService.js +0 -334
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { BaseService } from "./BaseService.js";
|
|
2
|
+
import { CollabClient } from "../utils/CollabClient.js";
|
|
3
|
+
import { RootStateManager } from "../state/RootStateManager.js";
|
|
4
|
+
import { rootBus } from "../state/rootEventBus.js";
|
|
5
|
+
class CollabService extends BaseService {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super(config);
|
|
8
|
+
this._client = null;
|
|
9
|
+
this._stateManager = null;
|
|
10
|
+
this._connected = false;
|
|
11
|
+
this._undoStack = [];
|
|
12
|
+
this._redoStack = [];
|
|
13
|
+
this._isUndoRedo = false;
|
|
14
|
+
this._pendingOps = [];
|
|
15
|
+
}
|
|
16
|
+
init({ context }) {
|
|
17
|
+
if (context == null ? void 0 : context.state) {
|
|
18
|
+
try {
|
|
19
|
+
this._stateManager = new RootStateManager(context.state);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
this._setError(err);
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
this._setReady();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Overridden to re-initialise the state manager once the root state becomes
|
|
29
|
+
* available via a subsequent SDK `updateContext()` call.
|
|
30
|
+
*/
|
|
31
|
+
updateContext(context = {}) {
|
|
32
|
+
super.updateContext(context);
|
|
33
|
+
if (context.state) {
|
|
34
|
+
this._stateManager = new RootStateManager(context.state);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Ensure that the state manager exists. This is called right before any
|
|
39
|
+
* operation that requires access to the root state (e.g. `connect()`).
|
|
40
|
+
* Throws an explicit error if the root state is still missing so that the
|
|
41
|
+
* caller can react accordingly.
|
|
42
|
+
*/
|
|
43
|
+
_ensureStateManager() {
|
|
44
|
+
var _a;
|
|
45
|
+
if (!this._stateManager) {
|
|
46
|
+
if (!((_a = this._context) == null ? void 0 : _a.state)) {
|
|
47
|
+
throw new Error("[CollabService] Cannot operate without root state");
|
|
48
|
+
}
|
|
49
|
+
this._stateManager = new RootStateManager(this._context.state);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/* ---------- Connection Management ---------- */
|
|
53
|
+
async connect(options = {}) {
|
|
54
|
+
var _a, _b, _c, _d, _e;
|
|
55
|
+
this._ensureStateManager();
|
|
56
|
+
const {
|
|
57
|
+
authToken: jwt,
|
|
58
|
+
projectId,
|
|
59
|
+
branch = "main",
|
|
60
|
+
pro
|
|
61
|
+
} = {
|
|
62
|
+
...this._context,
|
|
63
|
+
...options
|
|
64
|
+
};
|
|
65
|
+
console.log(jwt, projectId, branch, pro);
|
|
66
|
+
if (!projectId) {
|
|
67
|
+
throw new Error("projectId is required for CollabService connection");
|
|
68
|
+
}
|
|
69
|
+
if (this._client) {
|
|
70
|
+
await this.disconnect();
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
this._client = new CollabClient({
|
|
74
|
+
jwt,
|
|
75
|
+
projectId,
|
|
76
|
+
branch,
|
|
77
|
+
live: Boolean(pro)
|
|
78
|
+
});
|
|
79
|
+
await new Promise((resolve) => {
|
|
80
|
+
var _a2, _b2;
|
|
81
|
+
if ((_a2 = this._client.socket) == null ? void 0 : _a2.connected) {
|
|
82
|
+
resolve();
|
|
83
|
+
} else {
|
|
84
|
+
(_b2 = this._client.socket) == null ? void 0 : _b2.once("connect", resolve);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
console.log("[CollabService] socket connected");
|
|
88
|
+
(_a = this._client.socket) == null ? void 0 : _a.on("ops", ({ changes }) => {
|
|
89
|
+
console.log(`ops event`);
|
|
90
|
+
console.log(changes);
|
|
91
|
+
this._stateManager.applyChanges(changes, { fromSocket: true });
|
|
92
|
+
});
|
|
93
|
+
(_b = this._client.socket) == null ? void 0 : _b.on("commit", ({ version }) => {
|
|
94
|
+
this._stateManager.setVersion(version);
|
|
95
|
+
rootBus.emit("checkpoint:done", { version, origin: "auto" });
|
|
96
|
+
});
|
|
97
|
+
(_c = this._client.socket) == null ? void 0 : _c.on("clients", this._handleClientsEvent.bind(this));
|
|
98
|
+
(_d = this._client.socket) == null ? void 0 : _d.on("presence", this._handleClientsEvent.bind(this));
|
|
99
|
+
(_e = this._client.socket) == null ? void 0 : _e.on("cursor", this._handleCursorEvent.bind(this));
|
|
100
|
+
if (this._pendingOps.length) {
|
|
101
|
+
console.log(
|
|
102
|
+
`[CollabService] Flushing ${this._pendingOps.length} offline operation batch(es)`
|
|
103
|
+
);
|
|
104
|
+
this._pendingOps.forEach(({ tuples }) => {
|
|
105
|
+
this.socket.emit("ops", { changes: tuples, ts: Date.now() });
|
|
106
|
+
});
|
|
107
|
+
this._pendingOps.length = 0;
|
|
108
|
+
}
|
|
109
|
+
this._connected = true;
|
|
110
|
+
console.log("[CollabService] Connected to project:", projectId);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error("[CollabService] Connection failed:", err);
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
disconnect() {
|
|
117
|
+
var _a;
|
|
118
|
+
if ((_a = this._client) == null ? void 0 : _a.socket) {
|
|
119
|
+
this._client.socket.disconnect();
|
|
120
|
+
}
|
|
121
|
+
this._client = null;
|
|
122
|
+
this._connected = false;
|
|
123
|
+
console.log("[CollabService] Disconnected");
|
|
124
|
+
}
|
|
125
|
+
isConnected() {
|
|
126
|
+
var _a, _b;
|
|
127
|
+
return this._connected && ((_b = (_a = this._client) == null ? void 0 : _a.socket) == null ? void 0 : _b.connected);
|
|
128
|
+
}
|
|
129
|
+
/* convenient shortcuts */
|
|
130
|
+
get ydoc() {
|
|
131
|
+
var _a;
|
|
132
|
+
return (_a = this._client) == null ? void 0 : _a.ydoc;
|
|
133
|
+
}
|
|
134
|
+
get socket() {
|
|
135
|
+
var _a;
|
|
136
|
+
return (_a = this._client) == null ? void 0 : _a.socket;
|
|
137
|
+
}
|
|
138
|
+
toggleLive(f) {
|
|
139
|
+
var _a;
|
|
140
|
+
(_a = this._client) == null ? void 0 : _a.toggleLive(f);
|
|
141
|
+
}
|
|
142
|
+
sendCursor(d) {
|
|
143
|
+
var _a;
|
|
144
|
+
(_a = this._client) == null ? void 0 : _a.sendCursor(d);
|
|
145
|
+
}
|
|
146
|
+
sendPresence(d) {
|
|
147
|
+
var _a;
|
|
148
|
+
(_a = this._client) == null ? void 0 : _a.sendPresence(d);
|
|
149
|
+
}
|
|
150
|
+
/* ---------- data helpers ---------- */
|
|
151
|
+
updateData(tuples, options = {}) {
|
|
152
|
+
var _a;
|
|
153
|
+
this._ensureStateManager();
|
|
154
|
+
const { isUndo = false, isRedo = false } = options;
|
|
155
|
+
if (!isUndo && !isRedo && !this._isUndoRedo) {
|
|
156
|
+
this._trackForUndo(tuples, options);
|
|
157
|
+
}
|
|
158
|
+
this._stateManager.applyChanges(tuples, { ...options });
|
|
159
|
+
if (!this.isConnected()) {
|
|
160
|
+
console.warn("[CollabService] Not connected, queuing real-time update");
|
|
161
|
+
this._pendingOps.push({ tuples, options });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if ((_a = this.socket) == null ? void 0 : _a.connected) {
|
|
165
|
+
this.socket.emit("ops", { changes: tuples, ts: Date.now() });
|
|
166
|
+
}
|
|
167
|
+
return { success: true };
|
|
168
|
+
}
|
|
169
|
+
_trackForUndo(tuples, options) {
|
|
170
|
+
var _a;
|
|
171
|
+
const undoOperations = tuples.map((tuple) => {
|
|
172
|
+
const [action, path] = tuple;
|
|
173
|
+
const currentValue = this._getValueAtPath(path);
|
|
174
|
+
if (action === "delete") {
|
|
175
|
+
return ["update", path, currentValue];
|
|
176
|
+
}
|
|
177
|
+
if (typeof currentValue !== "undefined") {
|
|
178
|
+
return ["update", path, currentValue];
|
|
179
|
+
}
|
|
180
|
+
return ["delete", path];
|
|
181
|
+
});
|
|
182
|
+
this._undoStack.push({
|
|
183
|
+
operations: undoOperations,
|
|
184
|
+
originalOperations: tuples,
|
|
185
|
+
options,
|
|
186
|
+
timestamp: Date.now()
|
|
187
|
+
});
|
|
188
|
+
this._redoStack.length = 0;
|
|
189
|
+
const maxUndoSteps = ((_a = this._options) == null ? void 0 : _a.maxUndoSteps) || 50;
|
|
190
|
+
if (this._undoStack.length > maxUndoSteps) {
|
|
191
|
+
this._undoStack.shift();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
_getValueAtPath(path) {
|
|
195
|
+
var _a;
|
|
196
|
+
const state = (_a = this._stateManager) == null ? void 0 : _a.root;
|
|
197
|
+
if (!state || !state.getByPath) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
return state.getByPath(path);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.warn("[CollabService] Could not get value at path:", path, error);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
undo() {
|
|
208
|
+
if (!this._undoStack.length) {
|
|
209
|
+
throw new Error("Nothing to undo");
|
|
210
|
+
}
|
|
211
|
+
if (!this.isConnected()) {
|
|
212
|
+
console.warn("[CollabService] Not connected, cannot undo");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const undoItem = this._undoStack.pop();
|
|
216
|
+
const { operations, originalOperations, options } = undoItem;
|
|
217
|
+
this._redoStack.push({
|
|
218
|
+
operations: originalOperations,
|
|
219
|
+
originalOperations: operations,
|
|
220
|
+
options,
|
|
221
|
+
timestamp: Date.now()
|
|
222
|
+
});
|
|
223
|
+
this._isUndoRedo = true;
|
|
224
|
+
try {
|
|
225
|
+
this.updateData(operations, {
|
|
226
|
+
...options,
|
|
227
|
+
isUndo: true,
|
|
228
|
+
message: `Undo: ${options.message || "operation"}`
|
|
229
|
+
});
|
|
230
|
+
} finally {
|
|
231
|
+
this._isUndoRedo = false;
|
|
232
|
+
}
|
|
233
|
+
return operations;
|
|
234
|
+
}
|
|
235
|
+
redo() {
|
|
236
|
+
if (!this._redoStack.length) {
|
|
237
|
+
throw new Error("Nothing to redo");
|
|
238
|
+
}
|
|
239
|
+
if (!this.isConnected()) {
|
|
240
|
+
console.warn("[CollabService] Not connected, cannot redo");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const redoItem = this._redoStack.pop();
|
|
244
|
+
const { operations, originalOperations, options } = redoItem;
|
|
245
|
+
this._undoStack.push({
|
|
246
|
+
operations: originalOperations,
|
|
247
|
+
originalOperations: operations,
|
|
248
|
+
options,
|
|
249
|
+
timestamp: Date.now()
|
|
250
|
+
});
|
|
251
|
+
this._isUndoRedo = true;
|
|
252
|
+
try {
|
|
253
|
+
this.updateData(operations, {
|
|
254
|
+
...options,
|
|
255
|
+
isRedo: true,
|
|
256
|
+
message: `Redo: ${options.message || "operation"}`
|
|
257
|
+
});
|
|
258
|
+
} finally {
|
|
259
|
+
this._isUndoRedo = false;
|
|
260
|
+
}
|
|
261
|
+
return operations;
|
|
262
|
+
}
|
|
263
|
+
/* ---------- Undo/Redo State ---------- */
|
|
264
|
+
canUndo() {
|
|
265
|
+
return this._undoStack.length > 0;
|
|
266
|
+
}
|
|
267
|
+
canRedo() {
|
|
268
|
+
return this._redoStack.length > 0;
|
|
269
|
+
}
|
|
270
|
+
getUndoStackSize() {
|
|
271
|
+
return this._undoStack.length;
|
|
272
|
+
}
|
|
273
|
+
getRedoStackSize() {
|
|
274
|
+
return this._redoStack.length;
|
|
275
|
+
}
|
|
276
|
+
clearUndoHistory() {
|
|
277
|
+
this._undoStack.length = 0;
|
|
278
|
+
this._redoStack.length = 0;
|
|
279
|
+
}
|
|
280
|
+
addItem(type, data, opts = {}) {
|
|
281
|
+
const { value, ...schema } = data;
|
|
282
|
+
const tuples = [
|
|
283
|
+
["update", [type, data.key], value],
|
|
284
|
+
["update", ["schema", type, data.key], schema],
|
|
285
|
+
...opts.additionalChanges || []
|
|
286
|
+
];
|
|
287
|
+
return this.updateData(tuples, opts);
|
|
288
|
+
}
|
|
289
|
+
addMultipleItems(items, opts = {}) {
|
|
290
|
+
const tuples = [];
|
|
291
|
+
items.forEach(([type, data]) => {
|
|
292
|
+
const { value, ...schema } = data;
|
|
293
|
+
tuples.push(
|
|
294
|
+
["update", [type, data.key], value],
|
|
295
|
+
["update", ["schema", type, data.key], schema]
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
this.updateData([...tuples, ...opts.additionalChanges || []], {
|
|
299
|
+
message: `Created ${tuples.length} items`,
|
|
300
|
+
...opts
|
|
301
|
+
});
|
|
302
|
+
return tuples;
|
|
303
|
+
}
|
|
304
|
+
updateItem(type, data, opts = {}) {
|
|
305
|
+
const { value, ...schema } = data;
|
|
306
|
+
const tuples = [
|
|
307
|
+
["update", [type, data.key], value],
|
|
308
|
+
["update", ["schema", type, data.key], schema]
|
|
309
|
+
];
|
|
310
|
+
return this.updateData(tuples, opts);
|
|
311
|
+
}
|
|
312
|
+
deleteItem(type, key, opts = {}) {
|
|
313
|
+
const tuples = [
|
|
314
|
+
["delete", [type, key]],
|
|
315
|
+
["delete", ["schema", type, key]],
|
|
316
|
+
...opts.additionalChanges || []
|
|
317
|
+
];
|
|
318
|
+
return this.updateData(tuples, {
|
|
319
|
+
message: `Deleted ${key} from ${type}`,
|
|
320
|
+
...opts
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/* ---------- socket event helpers ---------- */
|
|
324
|
+
/**
|
|
325
|
+
* Handle "clients" or "presence" events coming from the collab socket.
|
|
326
|
+
* The backend sends the full `clients` object which we directly patch to
|
|
327
|
+
* the root state, mimicking the legacy SocketService behaviour so that
|
|
328
|
+
* existing UI components keep working unmodified.
|
|
329
|
+
*/
|
|
330
|
+
_handleClientsEvent(data = {}) {
|
|
331
|
+
var _a;
|
|
332
|
+
const root = (_a = this._stateManager) == null ? void 0 : _a.root;
|
|
333
|
+
if (root && typeof root.replace === "function") {
|
|
334
|
+
root.replace(
|
|
335
|
+
{ clients: data },
|
|
336
|
+
{
|
|
337
|
+
fromSocket: true,
|
|
338
|
+
preventUpdate: true
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Handle granular cursor updates coming from the socket.
|
|
345
|
+
* Expected payload: { userId, positions?, chosenPos?, ... }
|
|
346
|
+
* Only the provided fields are patched into the state tree.
|
|
347
|
+
*/
|
|
348
|
+
_handleCursorEvent(payload = {}) {
|
|
349
|
+
const { userId, positions, chosenPos, ...rest } = payload || {};
|
|
350
|
+
if (!userId) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const tuples = [];
|
|
354
|
+
if (positions) {
|
|
355
|
+
tuples.push([
|
|
356
|
+
"update",
|
|
357
|
+
["canvas", "clients", userId, "positions"],
|
|
358
|
+
positions
|
|
359
|
+
]);
|
|
360
|
+
}
|
|
361
|
+
if (chosenPos) {
|
|
362
|
+
tuples.push([
|
|
363
|
+
"update",
|
|
364
|
+
["canvas", "clients", userId, "chosenPos"],
|
|
365
|
+
chosenPos
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
if (Object.keys(rest).length) {
|
|
369
|
+
tuples.push(["update", ["canvas", "clients", userId], rest]);
|
|
370
|
+
}
|
|
371
|
+
if (tuples.length) {
|
|
372
|
+
this._stateManager.applyChanges(tuples, { fromSocket: true });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/* ---------- Manual checkpoint ---------- */
|
|
376
|
+
/**
|
|
377
|
+
* Manually request a checkpoint / commit of buffered operations on the server.
|
|
378
|
+
* Resolves with the new version number once the backend confirms via the
|
|
379
|
+
* regular "commit" event.
|
|
380
|
+
*/
|
|
381
|
+
checkpoint() {
|
|
382
|
+
if (!this.isConnected()) {
|
|
383
|
+
console.warn("[CollabService] Not connected, cannot request checkpoint");
|
|
384
|
+
return Promise.reject(new Error("Not connected"));
|
|
385
|
+
}
|
|
386
|
+
return new Promise((resolve) => {
|
|
387
|
+
var _a, _b;
|
|
388
|
+
const handler = ({ version }) => {
|
|
389
|
+
var _a2;
|
|
390
|
+
(_a2 = this.socket) == null ? void 0 : _a2.off("commit", handler);
|
|
391
|
+
rootBus.emit("checkpoint:done", { version, origin: "manual" });
|
|
392
|
+
resolve(version);
|
|
393
|
+
};
|
|
394
|
+
(_a = this.socket) == null ? void 0 : _a.once("commit", handler);
|
|
395
|
+
(_b = this.socket) == null ? void 0 : _b.emit("checkpoint");
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
export {
|
|
400
|
+
CollabService
|
|
401
|
+
};
|