124 lines
3.6 KiB
JavaScript
124 lines
3.6 KiB
JavaScript
// resources/js/composables/useSyncData.js
|
|
import { onMounted, onUnmounted } from 'vue';
|
|
import { useSyncStore } from '../stores/syncState.js';
|
|
|
|
/**
|
|
* Composable to handle real-time data synchronization using SSE or polling.
|
|
*/
|
|
export function useSyncData() {
|
|
const syncStore = useSyncStore();
|
|
let eventSource = null;
|
|
const intervals = {};
|
|
|
|
/**
|
|
* Start Server-Sent Events listener.
|
|
* @param {string} endpoint
|
|
*/
|
|
const startSSE = (endpoint = '/api/sync/events') => {
|
|
if (typeof EventSource === 'undefined') {
|
|
console.warn('[Sync] EventSource not supported by browser.');
|
|
return false;
|
|
}
|
|
|
|
if (eventSource) eventSource.close();
|
|
|
|
eventSource = new EventSource(endpoint);
|
|
|
|
eventSource.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
handleSyncMessage(data);
|
|
} catch (e) {
|
|
console.error('[Sync] Failed to parse SSE message:', e);
|
|
}
|
|
};
|
|
|
|
eventSource.onerror = (err) => {
|
|
console.error('[Sync] EventSource connection error, closing.');
|
|
eventSource.close();
|
|
eventSource = null;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Handle incoming synchronization messages.
|
|
* @param {object} payload
|
|
*/
|
|
const handleSyncMessage = (payload) => {
|
|
const { type, key, data } = payload;
|
|
|
|
syncStore.updateStatus(key || type, 'synced');
|
|
|
|
// Notify Pinia or other stores about updates
|
|
// For example:
|
|
// switch (type) {
|
|
// case 'user_update':
|
|
// useUserStore().fetchCurrentUser();
|
|
// break;
|
|
// }
|
|
};
|
|
|
|
/**
|
|
* Start interval-based polling as a fallback or for specific keys.
|
|
* @param {string} key
|
|
* @param {string} url
|
|
* @param {number} intervalMs
|
|
* @param {string} method
|
|
* @param {object|null} data
|
|
*/
|
|
const startPolling = (key, url, intervalMs = 60000, method = 'GET', data = null) => {
|
|
if (intervals[key]) return;
|
|
|
|
const pollFunc = async () => {
|
|
try {
|
|
syncStore.updateStatus(key, 'syncing');
|
|
const options = {
|
|
method: method,
|
|
headers: { 'Accept': 'application/json' }
|
|
};
|
|
if (data && (method === 'POST' || method === 'PUT')) {
|
|
options.headers['Content-Type'] = 'application/json';
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
const response = await fetch(url, options);
|
|
if (!response.ok) throw new Error(`Poll failed: ${response.status}`);
|
|
|
|
const result = await response.json();
|
|
handleSyncMessage({ type: 'poll_result', key, data: result });
|
|
} catch (err) {
|
|
syncStore.setError(key, err.message);
|
|
}
|
|
};
|
|
|
|
// Run immediately then start interval
|
|
pollFunc();
|
|
intervals[key] = setInterval(pollFunc, intervalMs);
|
|
};
|
|
|
|
/**
|
|
* Stop polling for a specific key.
|
|
* @param {string} key
|
|
*/
|
|
const stopPolling = (key) => {
|
|
if (intervals[key]) {
|
|
clearInterval(intervals[key]);
|
|
delete intervals[key];
|
|
}
|
|
};
|
|
|
|
// Cleanup on unmount
|
|
onUnmounted(() => {
|
|
if (eventSource) eventSource.close();
|
|
Object.values(intervals).forEach(clearInterval);
|
|
});
|
|
|
|
return {
|
|
startSSE,
|
|
startPolling,
|
|
stopPolling
|
|
};
|
|
}
|