@affectively/dash 5.0.1 → 5.1.1
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 +21 -0
- package/dist/src/api/firebase/auth/index.d.ts +137 -0
- package/dist/src/api/firebase/auth/index.js +352 -0
- package/dist/src/api/firebase/auth/providers.d.ts +254 -0
- package/dist/src/api/firebase/auth/providers.js +518 -0
- package/dist/src/api/firebase/database/index.d.ts +104 -0
- package/dist/src/api/firebase/database/index.js +351 -0
- package/dist/src/api/firebase/errors.d.ts +15 -0
- package/dist/src/api/firebase/errors.js +215 -0
- package/dist/src/api/firebase/firestore/data-types.d.ts +116 -0
- package/dist/src/api/firebase/firestore/data-types.js +280 -0
- package/dist/src/api/firebase/firestore/index.d.ts +7 -0
- package/dist/src/api/firebase/firestore/index.js +13 -0
- package/dist/src/api/firebase/firestore/listeners.d.ts +20 -0
- package/dist/src/api/firebase/firestore/listeners.js +50 -0
- package/dist/src/api/firebase/firestore/operations.d.ts +123 -0
- package/dist/src/api/firebase/firestore/operations.js +490 -0
- package/dist/src/api/firebase/firestore/query.d.ts +118 -0
- package/dist/src/api/firebase/firestore/query.js +418 -0
- package/dist/src/api/firebase/index.d.ts +11 -0
- package/dist/src/api/firebase/index.js +16 -0
- package/dist/src/api/firebase/storage/index.d.ts +100 -0
- package/dist/src/api/firebase/storage/index.js +286 -0
- package/dist/src/api/firebase/types.d.ts +340 -0
- package/dist/src/api/firebase/types.js +4 -0
- package/dist/src/auth/manager.d.ts +5 -1
- package/dist/src/auth/manager.js +19 -6
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +10 -0
- package/dist/src/sync/aeon/config.d.ts +21 -0
- package/dist/src/sync/aeon/config.js +14 -0
- package/dist/src/sync/aeon/delta-adapter.d.ts +62 -0
- package/dist/src/sync/aeon/delta-adapter.js +98 -0
- package/dist/src/sync/aeon/index.d.ts +18 -0
- package/dist/src/sync/aeon/index.js +19 -0
- package/dist/src/sync/aeon/offline-adapter.d.ts +110 -0
- package/dist/src/sync/aeon/offline-adapter.js +227 -0
- package/dist/src/sync/aeon/presence-adapter.d.ts +114 -0
- package/dist/src/sync/aeon/presence-adapter.js +157 -0
- package/dist/src/sync/aeon/schema-adapter.d.ts +95 -0
- package/dist/src/sync/aeon/schema-adapter.js +163 -0
- package/dist/src/sync/hybrid-provider.d.ts +67 -2
- package/dist/src/sync/hybrid-provider.js +128 -9
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -1
package/dist/src/index.js
CHANGED
|
@@ -5,3 +5,13 @@ export { mcpServer } from './mcp/server.js';
|
|
|
5
5
|
export { YjsSqliteProvider } from './sync/provider.js';
|
|
6
6
|
export { backup, restore, generateKey, exportKey, importKey } from './sync/backup.js';
|
|
7
7
|
export { HybridProvider } from './sync/hybrid-provider.js';
|
|
8
|
+
// Firebase Compatibility API exports
|
|
9
|
+
export * as firebase from './api/firebase/index.js';
|
|
10
|
+
// Firestore
|
|
11
|
+
export { collection, doc, query, where, orderBy, limit, limitToLast, startAt, startAfter, endAt, endBefore, offset, serverTimestamp, deleteField, arrayUnion, arrayRemove, increment, getDoc, getDocs, setDoc, updateDoc, deleteDoc, writeBatch, runTransaction, onSnapshot, } from './api/firebase/index.js';
|
|
12
|
+
// Realtime Database
|
|
13
|
+
export { ref, child, set, update, remove, push, get, onDisconnect, onValue, onChildAdded, onChildChanged, onChildRemoved, onChildMoved, off, } from './api/firebase/index.js';
|
|
14
|
+
// Authentication
|
|
15
|
+
export { createUserWithEmailAndPassword, signInWithEmailAndPassword, signInAnonymously, signOut, getAuth, onAuthStateChanged, updateUserProfile, updateUserEmail, updateUserPassword, deleteUser, sendPasswordResetEmail, sendEmailVerification, GoogleAuthProvider, GithubAuthProvider, FacebookAuthProvider, TwitterAuthProvider, DiscordAuthProvider, TotpMultiFactorGenerator, PhoneMultiFactorGenerator, setPersistence, browserLocalPersistence, browserSessionPersistence, inMemoryPersistence, } from './api/firebase/index.js';
|
|
16
|
+
// Storage
|
|
17
|
+
export { ref as storageRef, refFromURL, child as storageChild, uploadBytes, uploadBytesResumable, uploadString, getBytes, getDownloadURL, getMetadata, updateMetadata, deleteObject, list as listFiles, listAll, } from './api/firebase/index.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aeon Integration Configuration
|
|
3
|
+
*
|
|
4
|
+
* Configuration for Aeon sync features in Dash.
|
|
5
|
+
* All features enabled by default.
|
|
6
|
+
*/
|
|
7
|
+
export interface AeonConfig {
|
|
8
|
+
/** Enable delta sync optimization (70-80% bandwidth reduction) */
|
|
9
|
+
enableDeltaSync: boolean;
|
|
10
|
+
/** Enable rich presence tracking (cursors, sections, activity) */
|
|
11
|
+
enableRichPresence: boolean;
|
|
12
|
+
/** Enable offline operation queuing */
|
|
13
|
+
enableOfflineQueue: boolean;
|
|
14
|
+
/** Bytes threshold before falling back to full sync instead of delta */
|
|
15
|
+
deltaThreshold: number;
|
|
16
|
+
/** Maximum operations to hold in offline queue */
|
|
17
|
+
maxOfflineQueueSize: number;
|
|
18
|
+
/** Maximum retries for failed offline operations */
|
|
19
|
+
maxOfflineRetries: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const defaultAeonConfig: AeonConfig;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aeon Integration Configuration
|
|
3
|
+
*
|
|
4
|
+
* Configuration for Aeon sync features in Dash.
|
|
5
|
+
* All features enabled by default.
|
|
6
|
+
*/
|
|
7
|
+
export const defaultAeonConfig = {
|
|
8
|
+
enableDeltaSync: true,
|
|
9
|
+
enableRichPresence: true,
|
|
10
|
+
enableOfflineQueue: true,
|
|
11
|
+
deltaThreshold: 1000,
|
|
12
|
+
maxOfflineQueueSize: 1000,
|
|
13
|
+
maxOfflineRetries: 3,
|
|
14
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta Adapter for Dash
|
|
3
|
+
*
|
|
4
|
+
* Wraps Aeon's DeltaSyncOptimizer to provide delta compression
|
|
5
|
+
* for Yjs document updates. Achieves 70-80% bandwidth reduction.
|
|
6
|
+
*/
|
|
7
|
+
import { type DeltaOperation, type DeltaStats } from '@affectively/aeon';
|
|
8
|
+
/**
|
|
9
|
+
* Adapter that wraps Yjs updates with delta compression
|
|
10
|
+
*/
|
|
11
|
+
export declare class DashDeltaAdapter {
|
|
12
|
+
private optimizer;
|
|
13
|
+
private roomId;
|
|
14
|
+
private updateCounter;
|
|
15
|
+
constructor(roomId: string, threshold?: number);
|
|
16
|
+
/**
|
|
17
|
+
* Wrap a Yjs update with delta compression
|
|
18
|
+
* Returns a delta payload ready for transmission
|
|
19
|
+
*/
|
|
20
|
+
wrapUpdate(update: Uint8Array, origin?: string): DeltaPayload;
|
|
21
|
+
/**
|
|
22
|
+
* Unwrap a delta payload back to Yjs update
|
|
23
|
+
*/
|
|
24
|
+
unwrapDelta(payload: DeltaPayload): Uint8Array;
|
|
25
|
+
/**
|
|
26
|
+
* Encode delta payload for wire transmission
|
|
27
|
+
*/
|
|
28
|
+
encodePayload(payload: DeltaPayload): Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* Decode delta payload from wire
|
|
31
|
+
*/
|
|
32
|
+
decodePayload(data: Uint8Array): DeltaPayload;
|
|
33
|
+
/**
|
|
34
|
+
* Get compression statistics
|
|
35
|
+
*/
|
|
36
|
+
getStats(): DeltaStats;
|
|
37
|
+
/**
|
|
38
|
+
* Reset statistics
|
|
39
|
+
*/
|
|
40
|
+
resetStats(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get memory footprint estimate
|
|
43
|
+
*/
|
|
44
|
+
getMemoryEstimate(): number;
|
|
45
|
+
/**
|
|
46
|
+
* Clear operation history
|
|
47
|
+
*/
|
|
48
|
+
clearHistory(): void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Delta payload structure for wire transmission
|
|
52
|
+
*/
|
|
53
|
+
export interface DeltaPayload {
|
|
54
|
+
type: 'delta';
|
|
55
|
+
delta: DeltaOperation;
|
|
56
|
+
originalSize: number;
|
|
57
|
+
deltaSize: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if a payload is a delta payload
|
|
61
|
+
*/
|
|
62
|
+
export declare function isDeltaPayload(data: unknown): data is DeltaPayload;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta Adapter for Dash
|
|
3
|
+
*
|
|
4
|
+
* Wraps Aeon's DeltaSyncOptimizer to provide delta compression
|
|
5
|
+
* for Yjs document updates. Achieves 70-80% bandwidth reduction.
|
|
6
|
+
*/
|
|
7
|
+
import { DeltaSyncOptimizer, } from '@affectively/aeon';
|
|
8
|
+
/**
|
|
9
|
+
* Adapter that wraps Yjs updates with delta compression
|
|
10
|
+
*/
|
|
11
|
+
export class DashDeltaAdapter {
|
|
12
|
+
optimizer;
|
|
13
|
+
roomId;
|
|
14
|
+
updateCounter = 0;
|
|
15
|
+
constructor(roomId, threshold = 1000) {
|
|
16
|
+
this.roomId = roomId;
|
|
17
|
+
this.optimizer = new DeltaSyncOptimizer(threshold);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Wrap a Yjs update with delta compression
|
|
21
|
+
* Returns a delta payload ready for transmission
|
|
22
|
+
*/
|
|
23
|
+
wrapUpdate(update, origin) {
|
|
24
|
+
const operationId = `${this.roomId}-${this.updateCounter++}`;
|
|
25
|
+
// Create an Aeon Operation from the Yjs update
|
|
26
|
+
const operation = {
|
|
27
|
+
id: operationId,
|
|
28
|
+
type: 'update',
|
|
29
|
+
sessionId: this.roomId,
|
|
30
|
+
data: {
|
|
31
|
+
update: Array.from(update),
|
|
32
|
+
origin: origin ?? 'local',
|
|
33
|
+
},
|
|
34
|
+
status: 'pending',
|
|
35
|
+
createdAt: Date.now(),
|
|
36
|
+
};
|
|
37
|
+
const delta = this.optimizer.computeDelta(operation);
|
|
38
|
+
return {
|
|
39
|
+
type: 'delta',
|
|
40
|
+
delta,
|
|
41
|
+
originalSize: update.byteLength,
|
|
42
|
+
deltaSize: new TextEncoder().encode(JSON.stringify(delta)).byteLength,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Unwrap a delta payload back to Yjs update
|
|
47
|
+
*/
|
|
48
|
+
unwrapDelta(payload) {
|
|
49
|
+
const operation = this.optimizer.decompressDelta(payload.delta);
|
|
50
|
+
return new Uint8Array(operation.data.update);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Encode delta payload for wire transmission
|
|
54
|
+
*/
|
|
55
|
+
encodePayload(payload) {
|
|
56
|
+
return new TextEncoder().encode(JSON.stringify(payload));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Decode delta payload from wire
|
|
60
|
+
*/
|
|
61
|
+
decodePayload(data) {
|
|
62
|
+
return JSON.parse(new TextDecoder().decode(data));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get compression statistics
|
|
66
|
+
*/
|
|
67
|
+
getStats() {
|
|
68
|
+
return this.optimizer.getStats();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reset statistics
|
|
72
|
+
*/
|
|
73
|
+
resetStats() {
|
|
74
|
+
this.optimizer.resetStats();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get memory footprint estimate
|
|
78
|
+
*/
|
|
79
|
+
getMemoryEstimate() {
|
|
80
|
+
return this.optimizer.getMemoryEstimate();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Clear operation history
|
|
84
|
+
*/
|
|
85
|
+
clearHistory() {
|
|
86
|
+
this.optimizer.resetStats();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a payload is a delta payload
|
|
91
|
+
*/
|
|
92
|
+
export function isDeltaPayload(data) {
|
|
93
|
+
return (typeof data === 'object' &&
|
|
94
|
+
data !== null &&
|
|
95
|
+
'type' in data &&
|
|
96
|
+
data.type === 'delta' &&
|
|
97
|
+
'delta' in data);
|
|
98
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aeon Integration for Dash
|
|
3
|
+
*
|
|
4
|
+
* Provides enhanced sync capabilities through Aeon:
|
|
5
|
+
* - Delta compression (70-80% bandwidth reduction)
|
|
6
|
+
* - Rich presence tracking (cursors, sections, activity)
|
|
7
|
+
* - Offline operation queuing with persistence
|
|
8
|
+
* - Schema versioning and migrations
|
|
9
|
+
*/
|
|
10
|
+
export { defaultAeonConfig, type AeonConfig } from './config.js';
|
|
11
|
+
export { DashDeltaAdapter, isDeltaPayload, type DeltaPayload, } from './delta-adapter.js';
|
|
12
|
+
export { DashPresenceAdapter, type PresenceEvents, } from './presence-adapter.js';
|
|
13
|
+
export { DashOfflineAdapter, type ProcessQueueResult, type OfflineQueueEvents, } from './offline-adapter.js';
|
|
14
|
+
export { DashSchemaAdapter, createSchemaAdapter, } from './schema-adapter.js';
|
|
15
|
+
export type { DeltaOperation, DeltaStats, DeltaBatch, } from '@affectively/aeon';
|
|
16
|
+
export type { AgentPresence, } from '@affectively/aeon';
|
|
17
|
+
export type { OfflineOperation, OfflineQueueStats, OperationPriority, } from '@affectively/aeon';
|
|
18
|
+
export type { SchemaVersion, Migration, MigrationResult, MigrationRecord, } from '@affectively/aeon';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aeon Integration for Dash
|
|
3
|
+
*
|
|
4
|
+
* Provides enhanced sync capabilities through Aeon:
|
|
5
|
+
* - Delta compression (70-80% bandwidth reduction)
|
|
6
|
+
* - Rich presence tracking (cursors, sections, activity)
|
|
7
|
+
* - Offline operation queuing with persistence
|
|
8
|
+
* - Schema versioning and migrations
|
|
9
|
+
*/
|
|
10
|
+
// Configuration
|
|
11
|
+
export { defaultAeonConfig } from './config.js';
|
|
12
|
+
// Delta compression adapter
|
|
13
|
+
export { DashDeltaAdapter, isDeltaPayload, } from './delta-adapter.js';
|
|
14
|
+
// Presence adapter
|
|
15
|
+
export { DashPresenceAdapter, } from './presence-adapter.js';
|
|
16
|
+
// Offline queue adapter
|
|
17
|
+
export { DashOfflineAdapter, } from './offline-adapter.js';
|
|
18
|
+
// Schema versioning adapter
|
|
19
|
+
export { DashSchemaAdapter, createSchemaAdapter, } from './schema-adapter.js';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline Adapter for Dash
|
|
3
|
+
*
|
|
4
|
+
* Wraps Aeon's OfflineOperationQueue to provide offline-first
|
|
5
|
+
* operation queuing for Yjs documents.
|
|
6
|
+
*/
|
|
7
|
+
import { type OfflineOperation, type OfflineQueueStats, type OperationPriority } from '@affectively/aeon';
|
|
8
|
+
import * as Y from 'yjs';
|
|
9
|
+
/**
|
|
10
|
+
* Adapter that queues Yjs updates when offline
|
|
11
|
+
*/
|
|
12
|
+
export declare class DashOfflineAdapter {
|
|
13
|
+
private queue;
|
|
14
|
+
private doc;
|
|
15
|
+
private roomId;
|
|
16
|
+
private storageKey;
|
|
17
|
+
private isOnline;
|
|
18
|
+
private processingQueue;
|
|
19
|
+
constructor(doc: Y.Doc, roomId: string, maxQueueSize?: number, maxRetries?: number);
|
|
20
|
+
/**
|
|
21
|
+
* Setup network status listeners
|
|
22
|
+
*/
|
|
23
|
+
private setupNetworkListeners;
|
|
24
|
+
/**
|
|
25
|
+
* Setup queue event handlers
|
|
26
|
+
*/
|
|
27
|
+
private setupQueueEvents;
|
|
28
|
+
/**
|
|
29
|
+
* Queue a Yjs update for later sync
|
|
30
|
+
*/
|
|
31
|
+
queueUpdate(update: Uint8Array, priority?: OperationPriority): OfflineOperation;
|
|
32
|
+
/**
|
|
33
|
+
* Check if we should queue (offline) or send directly
|
|
34
|
+
*/
|
|
35
|
+
shouldQueue(): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Process queued operations
|
|
38
|
+
* @param sendFn Function to send updates (typically HybridProvider.send)
|
|
39
|
+
*/
|
|
40
|
+
processQueue(sendFn?: (update: Uint8Array) => Promise<void>): Promise<ProcessQueueResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Get queue statistics
|
|
43
|
+
*/
|
|
44
|
+
getStats(): OfflineQueueStats;
|
|
45
|
+
/**
|
|
46
|
+
* Get pending operation count
|
|
47
|
+
*/
|
|
48
|
+
getPendingCount(): number;
|
|
49
|
+
/**
|
|
50
|
+
* Check if online
|
|
51
|
+
*/
|
|
52
|
+
getOnlineStatus(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Retry failed operations
|
|
55
|
+
*/
|
|
56
|
+
retryFailed(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Clear failed operations
|
|
59
|
+
*/
|
|
60
|
+
clearFailed(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Clear all queued operations
|
|
63
|
+
*/
|
|
64
|
+
clear(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Subscribe to queue events
|
|
67
|
+
*/
|
|
68
|
+
on<E extends keyof OfflineQueueEvents>(event: E, handler: OfflineQueueEvents[E]): void;
|
|
69
|
+
/**
|
|
70
|
+
* Unsubscribe from queue events
|
|
71
|
+
*/
|
|
72
|
+
off<E extends keyof OfflineQueueEvents>(event: E, handler: OfflineQueueEvents[E]): void;
|
|
73
|
+
/**
|
|
74
|
+
* Persist queue to localStorage
|
|
75
|
+
*/
|
|
76
|
+
private persistQueue;
|
|
77
|
+
/**
|
|
78
|
+
* Load queue from localStorage
|
|
79
|
+
*/
|
|
80
|
+
private loadPersistedQueue;
|
|
81
|
+
/**
|
|
82
|
+
* Clear persisted queue
|
|
83
|
+
*/
|
|
84
|
+
private clearPersistedQueue;
|
|
85
|
+
/**
|
|
86
|
+
* Cleanup
|
|
87
|
+
*/
|
|
88
|
+
destroy(): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Result of processing the queue
|
|
92
|
+
*/
|
|
93
|
+
export interface ProcessQueueResult {
|
|
94
|
+
synced: number;
|
|
95
|
+
failed: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Offline queue event types
|
|
99
|
+
*/
|
|
100
|
+
export interface OfflineQueueEvents {
|
|
101
|
+
'operation-added': (operation: OfflineOperation) => void;
|
|
102
|
+
'operation-synced': (operation: OfflineOperation) => void;
|
|
103
|
+
'operation-failed': (operation: OfflineOperation, error: Error) => void;
|
|
104
|
+
'queue-empty': () => void;
|
|
105
|
+
'sync-started': () => void;
|
|
106
|
+
'sync-completed': (stats: {
|
|
107
|
+
synced: number;
|
|
108
|
+
failed: number;
|
|
109
|
+
}) => void;
|
|
110
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline Adapter for Dash
|
|
3
|
+
*
|
|
4
|
+
* Wraps Aeon's OfflineOperationQueue to provide offline-first
|
|
5
|
+
* operation queuing for Yjs documents.
|
|
6
|
+
*/
|
|
7
|
+
import { OfflineOperationQueue, } from '@affectively/aeon';
|
|
8
|
+
import * as Y from 'yjs';
|
|
9
|
+
const STORAGE_KEY_PREFIX = 'dash-offline-queue-';
|
|
10
|
+
/**
|
|
11
|
+
* Adapter that queues Yjs updates when offline
|
|
12
|
+
*/
|
|
13
|
+
export class DashOfflineAdapter {
|
|
14
|
+
queue;
|
|
15
|
+
doc;
|
|
16
|
+
roomId;
|
|
17
|
+
storageKey;
|
|
18
|
+
isOnline = true;
|
|
19
|
+
processingQueue = false;
|
|
20
|
+
constructor(doc, roomId, maxQueueSize = 1000, maxRetries = 3) {
|
|
21
|
+
this.queue = new OfflineOperationQueue(maxQueueSize, maxRetries);
|
|
22
|
+
this.doc = doc;
|
|
23
|
+
this.roomId = roomId;
|
|
24
|
+
this.storageKey = `${STORAGE_KEY_PREFIX}${roomId}`;
|
|
25
|
+
// Load persisted queue on startup
|
|
26
|
+
this.loadPersistedQueue();
|
|
27
|
+
// Setup network status listeners
|
|
28
|
+
this.setupNetworkListeners();
|
|
29
|
+
// Setup queue event handlers
|
|
30
|
+
this.setupQueueEvents();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Setup network status listeners
|
|
34
|
+
*/
|
|
35
|
+
setupNetworkListeners() {
|
|
36
|
+
if (typeof window !== 'undefined') {
|
|
37
|
+
if (typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean') {
|
|
38
|
+
this.isOnline = navigator.onLine;
|
|
39
|
+
}
|
|
40
|
+
if (typeof window.addEventListener === 'function') {
|
|
41
|
+
window.addEventListener('online', () => {
|
|
42
|
+
this.isOnline = true;
|
|
43
|
+
this.processQueue();
|
|
44
|
+
});
|
|
45
|
+
window.addEventListener('offline', () => {
|
|
46
|
+
this.isOnline = false;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Setup queue event handlers
|
|
53
|
+
*/
|
|
54
|
+
setupQueueEvents() {
|
|
55
|
+
this.queue.on('operation-synced', (operation) => {
|
|
56
|
+
this.persistQueue();
|
|
57
|
+
});
|
|
58
|
+
this.queue.on('operation-failed', (operation, error) => {
|
|
59
|
+
console.warn(`[DashOfflineAdapter] Operation ${operation.id} failed: ${error.message}`);
|
|
60
|
+
this.persistQueue();
|
|
61
|
+
});
|
|
62
|
+
this.queue.on('queue-empty', () => {
|
|
63
|
+
this.clearPersistedQueue();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Queue a Yjs update for later sync
|
|
68
|
+
*/
|
|
69
|
+
queueUpdate(update, priority = 'normal') {
|
|
70
|
+
const operation = this.queue.enqueue('update', { update: Array.from(update) }, this.roomId, priority);
|
|
71
|
+
this.persistQueue();
|
|
72
|
+
return operation;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if we should queue (offline) or send directly
|
|
76
|
+
*/
|
|
77
|
+
shouldQueue() {
|
|
78
|
+
return !this.isOnline;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Process queued operations
|
|
82
|
+
* @param sendFn Function to send updates (typically HybridProvider.send)
|
|
83
|
+
*/
|
|
84
|
+
async processQueue(sendFn) {
|
|
85
|
+
if (!this.isOnline || this.processingQueue) {
|
|
86
|
+
return { synced: 0, failed: 0 };
|
|
87
|
+
}
|
|
88
|
+
this.processingQueue = true;
|
|
89
|
+
let synced = 0;
|
|
90
|
+
let failed = 0;
|
|
91
|
+
try {
|
|
92
|
+
while (true) {
|
|
93
|
+
const batch = this.queue.getNextBatch(10);
|
|
94
|
+
if (batch.length === 0)
|
|
95
|
+
break;
|
|
96
|
+
const ids = batch.map((op) => op.id);
|
|
97
|
+
this.queue.markSyncing(ids);
|
|
98
|
+
for (const operation of batch) {
|
|
99
|
+
try {
|
|
100
|
+
const update = new Uint8Array(operation.data.update);
|
|
101
|
+
if (sendFn) {
|
|
102
|
+
// Send via provided function
|
|
103
|
+
await sendFn(update);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Apply locally if no send function
|
|
107
|
+
Y.applyUpdate(this.doc, update);
|
|
108
|
+
}
|
|
109
|
+
this.queue.markSynced(operation.id);
|
|
110
|
+
synced++;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.queue.markFailed(operation.id, error);
|
|
114
|
+
failed++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
this.processingQueue = false;
|
|
121
|
+
this.persistQueue();
|
|
122
|
+
}
|
|
123
|
+
return { synced, failed };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get queue statistics
|
|
127
|
+
*/
|
|
128
|
+
getStats() {
|
|
129
|
+
return this.queue.getStats();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get pending operation count
|
|
133
|
+
*/
|
|
134
|
+
getPendingCount() {
|
|
135
|
+
return this.queue.getPendingCount();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if online
|
|
139
|
+
*/
|
|
140
|
+
getOnlineStatus() {
|
|
141
|
+
return this.isOnline;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Retry failed operations
|
|
145
|
+
*/
|
|
146
|
+
retryFailed() {
|
|
147
|
+
this.queue.retryFailed();
|
|
148
|
+
this.persistQueue();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Clear failed operations
|
|
152
|
+
*/
|
|
153
|
+
clearFailed() {
|
|
154
|
+
this.queue.clearFailed();
|
|
155
|
+
this.persistQueue();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Clear all queued operations
|
|
159
|
+
*/
|
|
160
|
+
clear() {
|
|
161
|
+
this.queue.clear();
|
|
162
|
+
this.clearPersistedQueue();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Subscribe to queue events
|
|
166
|
+
*/
|
|
167
|
+
on(event, handler) {
|
|
168
|
+
this.queue.on(event, handler);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Unsubscribe from queue events
|
|
172
|
+
*/
|
|
173
|
+
off(event, handler) {
|
|
174
|
+
this.queue.off(event, handler);
|
|
175
|
+
}
|
|
176
|
+
// ============================================
|
|
177
|
+
// Persistence
|
|
178
|
+
// ============================================
|
|
179
|
+
/**
|
|
180
|
+
* Persist queue to localStorage
|
|
181
|
+
*/
|
|
182
|
+
persistQueue() {
|
|
183
|
+
if (typeof localStorage !== 'undefined') {
|
|
184
|
+
try {
|
|
185
|
+
const operations = this.queue.export();
|
|
186
|
+
localStorage.setItem(this.storageKey, JSON.stringify(operations));
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
console.warn('[DashOfflineAdapter] Failed to persist queue:', error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Load queue from localStorage
|
|
195
|
+
*/
|
|
196
|
+
loadPersistedQueue() {
|
|
197
|
+
if (typeof localStorage !== 'undefined') {
|
|
198
|
+
try {
|
|
199
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
200
|
+
if (stored) {
|
|
201
|
+
const operations = JSON.parse(stored);
|
|
202
|
+
this.queue.import(operations);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.warn('[DashOfflineAdapter] Failed to load persisted queue:', error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Clear persisted queue
|
|
212
|
+
*/
|
|
213
|
+
clearPersistedQueue() {
|
|
214
|
+
if (typeof localStorage !== 'undefined') {
|
|
215
|
+
localStorage.removeItem(this.storageKey);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Cleanup
|
|
220
|
+
*/
|
|
221
|
+
destroy() {
|
|
222
|
+
this.queue.clear();
|
|
223
|
+
if (typeof window !== 'undefined') {
|
|
224
|
+
// Note: Can't easily remove anonymous listeners, but they'll be GC'd
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presence Adapter for Dash
|
|
3
|
+
*
|
|
4
|
+
* Bridges Yjs awareness protocol with Aeon's AgentPresenceManager.
|
|
5
|
+
* Provides rich presence features: cursor tracking, active sections, roles.
|
|
6
|
+
*/
|
|
7
|
+
import { type AgentPresence } from '@affectively/aeon';
|
|
8
|
+
import type { Awareness } from 'y-protocols/awareness';
|
|
9
|
+
/**
|
|
10
|
+
* Adapter that syncs Yjs awareness with Aeon presence
|
|
11
|
+
*/
|
|
12
|
+
export declare class DashPresenceAdapter {
|
|
13
|
+
private aeonPresence;
|
|
14
|
+
private yjsAwareness;
|
|
15
|
+
private localAgentId;
|
|
16
|
+
private roomId;
|
|
17
|
+
constructor(roomId: string, awareness: Awareness);
|
|
18
|
+
/**
|
|
19
|
+
* Sync Yjs awareness updates to Aeon presence
|
|
20
|
+
*/
|
|
21
|
+
private setupYjsToAeonSync;
|
|
22
|
+
/**
|
|
23
|
+
* Sync Aeon presence updates back to Yjs awareness
|
|
24
|
+
*/
|
|
25
|
+
private setupAeonToYjsSync;
|
|
26
|
+
/**
|
|
27
|
+
* Update local cursor position
|
|
28
|
+
*/
|
|
29
|
+
updateCursor(x: number, y: number, path: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Update local active section
|
|
32
|
+
*/
|
|
33
|
+
updateActiveSection(section: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Update local user info
|
|
36
|
+
*/
|
|
37
|
+
updateUserInfo(name: string, role?: AgentPresence['role']): void;
|
|
38
|
+
/**
|
|
39
|
+
* Get all online agents
|
|
40
|
+
*/
|
|
41
|
+
getOnlineAgents(): AgentPresence[];
|
|
42
|
+
/**
|
|
43
|
+
* Get all agents (including offline)
|
|
44
|
+
*/
|
|
45
|
+
getAllAgents(): AgentPresence[];
|
|
46
|
+
/**
|
|
47
|
+
* Get presence for specific agent
|
|
48
|
+
*/
|
|
49
|
+
getPresence(agentId: string): AgentPresence | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Get agents in a specific section
|
|
52
|
+
*/
|
|
53
|
+
getAgentsInSection(section: string): AgentPresence[];
|
|
54
|
+
/**
|
|
55
|
+
* Get presence statistics
|
|
56
|
+
*/
|
|
57
|
+
getStats(): {
|
|
58
|
+
total: number;
|
|
59
|
+
online: number;
|
|
60
|
+
away: number;
|
|
61
|
+
offline: number;
|
|
62
|
+
reconnecting: number;
|
|
63
|
+
byRole: Record<string, number>;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Get local agent ID
|
|
67
|
+
*/
|
|
68
|
+
getLocalAgentId(): string;
|
|
69
|
+
/**
|
|
70
|
+
* Subscribe to presence events
|
|
71
|
+
*/
|
|
72
|
+
on<E extends keyof PresenceEvents>(event: E, handler: PresenceEvents[E]): void;
|
|
73
|
+
/**
|
|
74
|
+
* Unsubscribe from presence events
|
|
75
|
+
*/
|
|
76
|
+
off<E extends keyof PresenceEvents>(event: E, handler: PresenceEvents[E]): void;
|
|
77
|
+
/**
|
|
78
|
+
* Cleanup
|
|
79
|
+
*/
|
|
80
|
+
destroy(): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Presence event types
|
|
84
|
+
*/
|
|
85
|
+
export interface PresenceEvents {
|
|
86
|
+
presence_updated: (data: {
|
|
87
|
+
agentId: string;
|
|
88
|
+
presence: AgentPresence;
|
|
89
|
+
}) => void;
|
|
90
|
+
agent_joined: (data: {
|
|
91
|
+
agentId: string;
|
|
92
|
+
presence: AgentPresence;
|
|
93
|
+
}) => void;
|
|
94
|
+
agent_left: (data: {
|
|
95
|
+
agentId: string;
|
|
96
|
+
presence: AgentPresence;
|
|
97
|
+
}) => void;
|
|
98
|
+
cursor_updated: (data: {
|
|
99
|
+
agentId: string;
|
|
100
|
+
cursorPosition: {
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
path: string;
|
|
104
|
+
};
|
|
105
|
+
}) => void;
|
|
106
|
+
section_updated: (data: {
|
|
107
|
+
agentId: string;
|
|
108
|
+
activeSection: string;
|
|
109
|
+
}) => void;
|
|
110
|
+
status_updated: (data: {
|
|
111
|
+
agentId: string;
|
|
112
|
+
status: AgentPresence['status'];
|
|
113
|
+
}) => void;
|
|
114
|
+
}
|