@affectively/dash 5.0.1 → 5.1.0

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.
@@ -6,6 +6,17 @@ import * as decoding from 'lib0/decoding';
6
6
  import * as syncProtocol from 'y-protocols/sync';
7
7
  // @ts-ignore
8
8
  import * as awarenessProtocol from 'y-protocols/awareness';
9
+ // Aeon integration
10
+ import { defaultAeonConfig } from './aeon/config.js';
11
+ import { DashDeltaAdapter } from './aeon/delta-adapter.js';
12
+ import { DashPresenceAdapter } from './aeon/presence-adapter.js';
13
+ import { DashOfflineAdapter } from './aeon/offline-adapter.js';
14
+ // Message types
15
+ const MessageType = {
16
+ Sync: 0,
17
+ Awareness: 1,
18
+ Delta: 2, // New: Aeon delta-compressed sync
19
+ };
9
20
  export class HybridProvider extends Observable {
10
21
  doc;
11
22
  ws = null;
@@ -17,12 +28,28 @@ export class HybridProvider extends Observable {
17
28
  writer = null;
18
29
  // "High Frequency" mode uses WebTransport for ephemeral data
19
30
  highFrequencyMode = false;
20
- constructor(url, roomName, doc, { awareness = new awarenessProtocol.Awareness(doc) } = {}) {
31
+ // Aeon integration
32
+ aeonConfig;
33
+ deltaAdapter = null;
34
+ presenceAdapter = null;
35
+ offlineAdapter = null;
36
+ constructor(url, roomName, doc, { awareness = new awarenessProtocol.Awareness(doc), aeonConfig = defaultAeonConfig, } = {}) {
21
37
  super();
22
38
  this.url = url;
23
39
  this.roomName = roomName;
24
40
  this.doc = doc;
25
41
  this.awareness = awareness;
42
+ this.aeonConfig = aeonConfig;
43
+ // Initialize Aeon adapters
44
+ if (aeonConfig.enableDeltaSync) {
45
+ this.deltaAdapter = new DashDeltaAdapter(roomName, aeonConfig.deltaThreshold);
46
+ }
47
+ if (aeonConfig.enableRichPresence) {
48
+ this.presenceAdapter = new DashPresenceAdapter(roomName, awareness);
49
+ }
50
+ if (aeonConfig.enableOfflineQueue) {
51
+ this.offlineAdapter = new DashOfflineAdapter(doc, roomName, aeonConfig.maxOfflineQueueSize, aeonConfig.maxOfflineRetries);
52
+ }
26
53
  this.doc.on('update', this.onDocUpdate.bind(this));
27
54
  this.awareness.on('update', this.onAwarenessUpdate.bind(this));
28
55
  this.connect();
@@ -140,19 +167,28 @@ export class HybridProvider extends Observable {
140
167
  }
141
168
  }
142
169
  handleMessage(buf) {
143
- // Simple protocol: First byte is message type (0=Sync, 1=Awareness)
170
+ // Simple protocol: First byte is message type (0=Sync, 1=Awareness, 2=Delta)
144
171
  // In strict binary mode, we might need a more robust header.
145
172
  // For now assuming Y-protocol encoding.
146
173
  // Note: The Relay DO sends raw messages back.
147
174
  const decoder = decoding.createDecoder(buf);
148
175
  const messageType = decoding.readVarUint(decoder);
149
176
  switch (messageType) {
150
- case 0: // Sync
177
+ case MessageType.Sync:
151
178
  syncProtocol.readSyncMessage(decoder, encoding.createEncoder(), this.doc, this);
152
179
  break;
153
- case 1: // Awareness
180
+ case MessageType.Awareness:
154
181
  awarenessProtocol.applyAwarenessUpdate(this.awareness, decoding.readVarUint8Array(decoder), this);
155
182
  break;
183
+ case MessageType.Delta:
184
+ // Handle Aeon delta-compressed sync
185
+ if (this.deltaAdapter) {
186
+ const deltaPayloadBytes = decoding.readVarUint8Array(decoder);
187
+ const deltaPayload = this.deltaAdapter.decodePayload(deltaPayloadBytes);
188
+ const update = this.deltaAdapter.unwrapDelta(deltaPayload);
189
+ Y.applyUpdate(this.doc, update, this);
190
+ }
191
+ break;
156
192
  }
157
193
  }
158
194
  async send(message) {
@@ -170,10 +206,26 @@ export class HybridProvider extends Observable {
170
206
  onDocUpdate(update, origin) {
171
207
  if (origin === this)
172
208
  return;
173
- const encoder = encoding.createEncoder();
174
- encoding.writeVarUint(encoder, 0); // MessageType.Sync
175
- syncProtocol.writeUpdate(encoder, update);
176
- this.send(encoding.toUint8Array(encoder));
209
+ // Check if we should queue (offline)
210
+ if (this.offlineAdapter?.shouldQueue()) {
211
+ this.offlineAdapter.queueUpdate(update);
212
+ return;
213
+ }
214
+ // Use delta compression if enabled
215
+ if (this.deltaAdapter) {
216
+ const deltaPayload = this.deltaAdapter.wrapUpdate(update, origin);
217
+ const encoder = encoding.createEncoder();
218
+ encoding.writeVarUint(encoder, MessageType.Delta);
219
+ encoding.writeVarUint8Array(encoder, this.deltaAdapter.encodePayload(deltaPayload));
220
+ this.send(encoding.toUint8Array(encoder));
221
+ }
222
+ else {
223
+ // Standard Yjs sync
224
+ const encoder = encoding.createEncoder();
225
+ encoding.writeVarUint(encoder, MessageType.Sync);
226
+ syncProtocol.writeUpdate(encoder, update);
227
+ this.send(encoding.toUint8Array(encoder));
228
+ }
177
229
  }
178
230
  onAwarenessUpdate({ added, updated, removed }, origin) {
179
231
  if (origin === this)
@@ -189,10 +241,61 @@ export class HybridProvider extends Observable {
189
241
  this.awareness.off('update', this.onAwarenessUpdate);
190
242
  this.ws?.close();
191
243
  this.wt?.close();
244
+ // Cleanup Aeon adapters
245
+ this.presenceAdapter?.destroy();
246
+ this.offlineAdapter?.destroy();
192
247
  this.connected = false;
193
248
  super.destroy();
194
249
  }
195
250
  // ============================================
251
+ // AEON ADAPTER ACCESSORS
252
+ // ============================================
253
+ /**
254
+ * Get the delta adapter for compression stats
255
+ */
256
+ getDeltaAdapter() {
257
+ return this.deltaAdapter;
258
+ }
259
+ /**
260
+ * Get the presence adapter for rich presence features
261
+ */
262
+ getPresenceAdapter() {
263
+ return this.presenceAdapter;
264
+ }
265
+ /**
266
+ * Get the offline adapter for queue management
267
+ */
268
+ getOfflineAdapter() {
269
+ return this.offlineAdapter;
270
+ }
271
+ /**
272
+ * Get Aeon configuration
273
+ */
274
+ getAeonConfig() {
275
+ return this.aeonConfig;
276
+ }
277
+ /**
278
+ * Process offline queue when back online
279
+ */
280
+ async processOfflineQueue() {
281
+ if (!this.offlineAdapter) {
282
+ return { synced: 0, failed: 0 };
283
+ }
284
+ return this.offlineAdapter.processQueue(async (update) => {
285
+ const encoder = encoding.createEncoder();
286
+ if (this.deltaAdapter) {
287
+ const deltaPayload = this.deltaAdapter.wrapUpdate(update);
288
+ encoding.writeVarUint(encoder, MessageType.Delta);
289
+ encoding.writeVarUint8Array(encoder, this.deltaAdapter.encodePayload(deltaPayload));
290
+ }
291
+ else {
292
+ encoding.writeVarUint(encoder, MessageType.Sync);
293
+ syncProtocol.writeUpdate(encoder, update);
294
+ }
295
+ await this.send(encoding.toUint8Array(encoder));
296
+ });
297
+ }
298
+ // ============================================
196
299
  // INTROSPECTION METHODS FOR DASH-STUDIO
197
300
  // ============================================
198
301
  /**
@@ -352,7 +455,23 @@ export class HybridProvider extends Observable {
352
455
  peerCount: this.getPeerCount(),
353
456
  states: this.getAwarenessStates()
354
457
  },
355
- document: this.getDocumentState()
458
+ document: this.getDocumentState(),
459
+ aeon: this.getAeonStatus()
460
+ };
461
+ }
462
+ /**
463
+ * Get Aeon-specific status
464
+ */
465
+ getAeonStatus() {
466
+ return {
467
+ enabled: {
468
+ deltaSync: this.aeonConfig.enableDeltaSync,
469
+ richPresence: this.aeonConfig.enableRichPresence,
470
+ offlineQueue: this.aeonConfig.enableOfflineQueue,
471
+ },
472
+ delta: this.deltaAdapter?.getStats() ?? null,
473
+ offline: this.offlineAdapter?.getStats() ?? null,
474
+ presence: this.presenceAdapter?.getStats() ?? null,
356
475
  };
357
476
  }
358
477
  }