initial: bootstrap from BukidBountyApp base
This commit is contained in:
123
resources/js/composables/useSyncData.js
Normal file
123
resources/js/composables/useSyncData.js
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user