aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
217 lines (214 loc) • 6.27 kB
JavaScript
import { io } from 'socket.io-client';
import { EventEmitter } from 'events';
class CollaborationService extends EventEmitter {
constructor(serverUrl, authToken) {
super();
this.serverUrl = serverUrl;
this.authToken = authToken;
this.socket = null;
this.currentRoom = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.operationQueue = [];
this.isConnected = false;
this.presenceMap = new Map();
this.userId = this.generateUserId();
this.userName = 'User';
}
async connect(userName) {
if (userName) this.userName = userName;
return new Promise((resolve, reject) => {
this.socket = io(this.serverUrl, {
auth: {
token: this.authToken,
userId: this.userId,
userName: this.userName
},
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: this.maxReconnectAttempts
});
this.setupEventHandlers();
this.socket.on('connect', () => {
this.isConnected = true;
this.reconnectAttempts = 0;
this.flushOperationQueue();
this.emit('connected');
resolve();
});
this.socket.on('connect_error', error => {
this.reconnectAttempts++;
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
reject(new Error(`Failed to connect: ${error.message}`));
}
});
});
}
setupEventHandlers() {
if (!this.socket) return;
this.socket.on('disconnect', () => {
this.isConnected = false;
this.emit('disconnected');
});
this.socket.on('cursor-update', data => {
this.updatePresenceCursor(data);
this.emit('cursor-moved', data);
});
this.socket.on('operation-applied', operation => {
this.emit('document-changed', operation);
});
this.socket.on('presence-update', presence => {
this.presenceMap.set(presence.userId, presence);
this.emit('presence-changed', Array.from(this.presenceMap.values()));
});
this.socket.on('user-joined', user => {
this.presenceMap.set(user.userId, user);
this.emit('user-joined', user);
});
this.socket.on('user-left', userId => {
this.presenceMap.delete(userId);
this.emit('user-left', userId);
});
this.socket.on('room-state', state => {
this.currentRoom = state.roomId;
state.participants.forEach(p => this.presenceMap.set(p.userId, p));
this.emit('room-synced', state);
});
this.socket.on('conflict-detected', conflict => {
this.emit('conflict', conflict);
});
}
async joinRoom(roomId) {
if (!this.socket || !this.isConnected) {
throw new Error('Not connected to collaboration server');
}
return new Promise((resolve, reject) => {
this.socket.emit('join-room', roomId, response => {
if (response.error) {
reject(new Error(response.error));
} else {
this.currentRoom = roomId;
resolve();
}
});
});
}
async leaveRoom() {
if (!this.socket || !this.currentRoom) return;
return new Promise(resolve => {
this.socket.emit('leave-room', this.currentRoom, () => {
this.currentRoom = null;
this.presenceMap.clear();
resolve();
});
});
}
sendCursorPosition(x, y) {
if (!this.socket || !this.isConnected || !this.currentRoom) return;
const cursorData = {
x,
y,
userId: this.userId,
userName: this.userName,
color: this.getUserColor()
};
this.socket.emit('cursor-move', cursorData);
}
sendEdit(edit) {
const fullEdit = {
...edit,
userId: this.userId,
timestamp: Date.now()
};
if (!this.socket || !this.isConnected) {
this.operationQueue.push(fullEdit);
return;
}
this.socket.emit('collaborative-edit', fullEdit);
}
updatePresence(status) {
if (!this.socket || !this.isConnected) return;
const presence = {
userId: this.userId,
userName: this.userName,
status,
lastActivity: new Date()
};
this.socket.emit('update-presence', presence);
}
updateSelection(start, end) {
if (!this.socket || !this.isConnected) return;
this.socket.emit('selection-change', {
userId: this.userId,
selection: {
start,
end
}
});
}
async createRoom(initialState) {
if (!this.socket || !this.isConnected) {
throw new Error('Not connected to collaboration server');
}
return new Promise((resolve, reject) => {
this.socket.emit('create-room', {
initialState
}, response => {
if (response.error) {
reject(new Error(response.error));
} else {
resolve(response.roomId);
}
});
});
}
getRoomParticipants() {
return Array.from(this.presenceMap.values());
}
getParticipantCount() {
return this.presenceMap.size;
}
updatePresenceCursor(cursorData) {
const presence = this.presenceMap.get(cursorData.userId);
if (presence) {
presence.cursor = cursorData;
presence.lastActivity = new Date();
this.presenceMap.set(cursorData.userId, presence);
}
}
flushOperationQueue() {
if (!this.socket || !this.isConnected) return;
while (this.operationQueue.length > 0) {
const operation = this.operationQueue.shift();
if (operation) {
this.socket.emit('collaborative-edit', operation);
}
}
}
generateUserId() {
return `user-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
getUserColor() {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F'];
const index = parseInt(this.userId.substr(-2), 36) % colors.length;
return colors[index];
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
this.isConnected = false;
this.currentRoom = null;
this.presenceMap.clear();
}
}
isConnectedToServer() {
return this.isConnected;
}
getCurrentRoom() {
return this.currentRoom;
}
}
export { CollaborationService };
//# sourceMappingURL=collaboration-service.js.map