import { Project } from "../models/project"; import { useEffect, useState } from "react"; import { generate as diff } from "json-merge-patch"; export interface ServerSyncOptions { projectURL: string refreshInterval: number } export const defaultOptions = { refreshInterval: 5000, projectURL: `//${window.location.host}/api/v1/projects`, } export function useServerSync(project: Project, applyServerUpdate: (project: Project) => void, options: ServerSyncOptions = defaultOptions) { options = Object.assign({}, defaultOptions, options); const [ serverState, setServerState ] = useState({ version: 0, project: project, }); console.log('server state', serverState); const handleAPIResponse = (res: Response) => { // If the project does not yet exist, create it if (res.status === 404) { return createProject(project, options) .then(res => res.json()) .then(result => { setServerState({ version: result.version, project }); }) ; } // In case of conflict, notify of new server version if (res.status === 409) { console.log("server conflict detected"); return res.json().then((result: any) => { console.log('received response', result); applyServerUpdate(result.project); setServerState({ version: result.version, project: result.project }); }); } // If the server version is not modified, do nothing if (res.status === 304) { console.log("no modification on server"); return; } return res.json().then((result: any) => { console.log('received response', result); applyServerUpdate(result.project); setServerState({ version: result.version, project: result.project }); }); }; // Force refresh periodically useEffect(() => { const intervalId = window.setInterval(() => { console.time("server-sync"); refreshProject(project, serverState.version, options).then(handleAPIResponse) .then(() => { console.timeEnd("server-sync"); }); }, options.refreshInterval); return () => clearInterval(intervalId); }, [serverState]); // Send delayed update useEffect(() => { const timeoutId = window.setTimeout(() => { const patch = diff(serverState.project, project); if (!patch) return; patchProject(project, patch, serverState.version, options).then(handleAPIResponse); }, 5000); return () => clearTimeout(timeoutId); }, [project, serverState]); } function refreshProject(project: Project, version: number, options: ServerSyncOptions): Promise { return fetch(`${options.projectURL}/${project.id}?version=${version}`, { method: 'GET' }); } function createProject(project: Project, options: ServerSyncOptions): Promise { return fetch(`${options.projectURL}/${project.id}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ project }) }); } function patchProject(project: Project, patch: Object, version: number, options: ServerSyncOptions): Promise { return fetch(`${options.projectURL}/${project.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ version, patch }) }); };