110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
import { Project } from "../models/project";
|
|
import { usePrevious } from "./use-previous";
|
|
import { useEffect, useState, useRef } from "preact/hooks";
|
|
import { generate as diff } from "json-merge-patch";
|
|
import useDebounce from "./use-debounce";
|
|
|
|
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<Response> {
|
|
return fetch(`${options.projectURL}/${project.id}?version=${version}`, {
|
|
method: 'GET'
|
|
});
|
|
}
|
|
|
|
function createProject(project: Project, options: ServerSyncOptions): Promise<Response> {
|
|
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<any> {
|
|
return fetch(`${options.projectURL}/${project.id}`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ version, patch })
|
|
});
|
|
};
|