feat: Implement multi-image product display with fading and hover effects, and introduce lazy-loaded HTML sanitization.
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
|
||||
class SocketManager {
|
||||
constructor() {
|
||||
this.socket = io('', {
|
||||
transports: ["websocket", "polling"],
|
||||
autoConnect: false
|
||||
});
|
||||
this._socket = null;
|
||||
this._socketReady = null;
|
||||
// Listeners registered before socket.io-client has loaded
|
||||
this._preSocketListeners = [];
|
||||
|
||||
this.emit = this.emit.bind(this);
|
||||
this.on = this.on.bind(this);
|
||||
@@ -14,30 +11,57 @@ class SocketManager {
|
||||
this.connectPromise = null;
|
||||
this.pendingListeners = new Map();
|
||||
}
|
||||
|
||||
on(event, callback) {
|
||||
// If socket is already connected, register the listener directly
|
||||
if (this.socket.connected) {
|
||||
this.socket.on(event, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the listener to be registered when connection is established
|
||||
if (!this.pendingListeners.has(event)) {
|
||||
this.pendingListeners.set(event, new Set());
|
||||
}
|
||||
this.pendingListeners.get(event).add(callback);
|
||||
|
||||
// Register the listener now, it will receive events once connected
|
||||
this.socket.on(event, callback);
|
||||
|
||||
// Lazily import socket.io-client and create the socket on first use.
|
||||
// Subsequent calls return the same promise.
|
||||
_ensureSocket() {
|
||||
if (this._socket) return Promise.resolve(this._socket);
|
||||
if (this._socketReady) return this._socketReady;
|
||||
|
||||
this._socketReady = import('socket.io-client').then(({ io }) => {
|
||||
this._socket = io('', {
|
||||
transports: ['websocket', 'polling'],
|
||||
autoConnect: false,
|
||||
});
|
||||
|
||||
// Register any listeners that arrived before the socket was ready
|
||||
this._preSocketListeners.forEach(({ event, callback }) => {
|
||||
this._socket.on(event, callback);
|
||||
});
|
||||
this._preSocketListeners = [];
|
||||
|
||||
return this._socket;
|
||||
});
|
||||
|
||||
return this._socketReady;
|
||||
}
|
||||
|
||||
|
||||
on(event, callback) {
|
||||
if (this._socket) {
|
||||
// Socket already loaded — mirror the original behaviour
|
||||
if (!this.pendingListeners.has(event)) {
|
||||
this.pendingListeners.set(event, new Set());
|
||||
}
|
||||
this.pendingListeners.get(event).add(callback);
|
||||
this._socket.on(event, callback);
|
||||
} else {
|
||||
// Queue for when socket.io-client finishes loading
|
||||
this._preSocketListeners.push({ event, callback });
|
||||
if (!this.pendingListeners.has(event)) {
|
||||
this.pendingListeners.set(event, new Set());
|
||||
}
|
||||
this.pendingListeners.get(event).add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
off(event, callback) {
|
||||
// Remove from socket listeners
|
||||
console.log('off', event, callback);
|
||||
this.socket.off(event, callback);
|
||||
|
||||
// Remove from pending listeners if present
|
||||
|
||||
// Remove from pre-socket queue (component unmounted before socket loaded)
|
||||
this._preSocketListeners = this._preSocketListeners.filter(
|
||||
(item) => !(item.event === event && item.callback === callback)
|
||||
);
|
||||
|
||||
if (this.pendingListeners.has(event)) {
|
||||
const listeners = this.pendingListeners.get(event);
|
||||
listeners.delete(callback);
|
||||
@@ -45,57 +69,60 @@ class SocketManager {
|
||||
this.pendingListeners.delete(event);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._socket) {
|
||||
this._socket.off(event, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
connect() {
|
||||
if (this.connectPromise) return this.connectPromise;
|
||||
|
||||
this.connectPromise = new Promise((resolve, reject) => {
|
||||
this.socket.connect();
|
||||
|
||||
this.socket.once('connect', () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
this.socket.once('connect_error', (error) => {
|
||||
this.connectPromise = null;
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
return this.connectPromise;
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.socket.connected) {
|
||||
// If not already connecting, start connection
|
||||
if (!this.connectPromise) {
|
||||
this.connect();
|
||||
}
|
||||
|
||||
// Wait for connection before emitting
|
||||
this.connectPromise
|
||||
.then(() => {
|
||||
this.socket.emit(event, ...args);
|
||||
|
||||
this.connectPromise = this._ensureSocket().then(
|
||||
(socket) =>
|
||||
new Promise((resolve, reject) => {
|
||||
socket.connect();
|
||||
socket.once('connect', () => {
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
});
|
||||
socket.once('connect_error', (error) => {
|
||||
this.connectPromise = null;
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
// Socket already connected, emit directly
|
||||
this.socket.emit(event, ...args);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return this.connectPromise;
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._ensureSocket()
|
||||
.then((socket) => {
|
||||
if (!socket.connected) {
|
||||
if (!this.connectPromise) {
|
||||
this.connect();
|
||||
}
|
||||
this.connectPromise
|
||||
.then(() => {
|
||||
socket.emit(event, ...args);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
socket.emit(event, ...args);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
// Create singleton instance and expose globally so all components can reach it
|
||||
const socketManager = new SocketManager();
|
||||
|
||||
// Attach to window object
|
||||
window.socketManager = socketManager;
|
||||
|
||||
export default socketManager;
|
||||
export default socketManager;
|
||||
|
||||
Reference in New Issue
Block a user