fix collab token refresh which leads to collab editor reconnection loop (#933)
This commit is contained in:
parent
37e760d76c
commit
c824b5b570
2 changed files with 43 additions and 20 deletions
|
|
@ -18,8 +18,10 @@ export function useCollabToken(): UseQueryResult<ICollabToken, Error> {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["collab-token"],
|
queryKey: ["collab-token"],
|
||||||
queryFn: () => getCollabToken(),
|
queryFn: () => getCollabToken(),
|
||||||
staleTime: 24 * 60 * 60 * 1000, //24hrs
|
staleTime: 20 * 60 * 60 * 1000, //20hrs
|
||||||
refetchInterval: 20 * 60 * 60 * 1000, //20hrs
|
refetchInterval: 12 * 60 * 60 * 1000, // 12hrs
|
||||||
|
refetchIntervalInBackground: true,
|
||||||
|
refetchOnMount: true,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
retry: (failureCount, error) => {
|
retry: (failureCount, error) => {
|
||||||
if (isAxiosError(error) && error.response.status === 404) {
|
if (isAxiosError(error) && error.response.status === 404) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ import React, {
|
||||||
} from "react";
|
} from "react";
|
||||||
import { IndexeddbPersistence } from "y-indexeddb";
|
import { IndexeddbPersistence } from "y-indexeddb";
|
||||||
import * as Y from "yjs";
|
import * as Y from "yjs";
|
||||||
import { HocuspocusProvider, WebSocketStatus } from "@hocuspocus/provider";
|
import {
|
||||||
|
HocuspocusProvider,
|
||||||
|
onAuthenticationFailedParameters,
|
||||||
|
WebSocketStatus,
|
||||||
|
} from "@hocuspocus/provider";
|
||||||
import { EditorContent, EditorProvider, useEditor } from "@tiptap/react";
|
import { EditorContent, EditorProvider, useEditor } from "@tiptap/react";
|
||||||
import {
|
import {
|
||||||
collabExtensions,
|
collabExtensions,
|
||||||
|
|
@ -41,16 +45,13 @@ import LinkMenu from "@/features/editor/components/link/link-menu.tsx";
|
||||||
import ExcalidrawMenu from "./components/excalidraw/excalidraw-menu";
|
import ExcalidrawMenu from "./components/excalidraw/excalidraw-menu";
|
||||||
import DrawioMenu from "./components/drawio/drawio-menu";
|
import DrawioMenu from "./components/drawio/drawio-menu";
|
||||||
import { useCollabToken } from "@/features/auth/queries/auth-query.tsx";
|
import { useCollabToken } from "@/features/auth/queries/auth-query.tsx";
|
||||||
import {
|
import { useDebouncedCallback, useDocumentVisibility } from "@mantine/hooks";
|
||||||
useDebouncedCallback,
|
|
||||||
useDocumentVisibility,
|
|
||||||
} from "@mantine/hooks";
|
|
||||||
import { useIdle } from "@/hooks/use-idle.ts";
|
import { useIdle } from "@/hooks/use-idle.ts";
|
||||||
import { FIVE_MINUTES } from "@/lib/constants.ts";
|
|
||||||
import { queryClient } from "@/main.tsx";
|
import { queryClient } from "@/main.tsx";
|
||||||
import { IPage } from "@/features/page/types/page.types.ts";
|
import { IPage } from "@/features/page/types/page.types.ts";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { extractPageSlugId } from "@/lib";
|
import { extractPageSlugId } from "@/lib";
|
||||||
|
import { FIVE_MINUTES } from "@/lib/constants.ts";
|
||||||
|
|
||||||
interface PageEditorProps {
|
interface PageEditorProps {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
|
|
@ -77,11 +78,12 @@ export default function PageEditor({
|
||||||
);
|
);
|
||||||
const menuContainerRef = useRef(null);
|
const menuContainerRef = useRef(null);
|
||||||
const documentName = `page.${pageId}`;
|
const documentName = `page.${pageId}`;
|
||||||
const { data } = useCollabToken();
|
const { data: collabQuery, refetch: refetchCollabToken } = useCollabToken();
|
||||||
const { isIdle, resetIdle } = useIdle(FIVE_MINUTES, { initialState: false });
|
const { isIdle, resetIdle } = useIdle(FIVE_MINUTES, { initialState: false });
|
||||||
const documentState = useDocumentVisibility();
|
const documentState = useDocumentVisibility();
|
||||||
const [isCollabReady, setIsCollabReady] = useState(false);
|
const [isCollabReady, setIsCollabReady] = useState(false);
|
||||||
const { pageSlug } = useParams();
|
const { pageSlug } = useParams();
|
||||||
|
const collabRetryCount = useRef(0);
|
||||||
const slugId = extractPageSlugId(pageSlug);
|
const slugId = extractPageSlugId(pageSlug);
|
||||||
|
|
||||||
const localProvider = useMemo(() => {
|
const localProvider = useMemo(() => {
|
||||||
|
|
@ -92,16 +94,26 @@ export default function PageEditor({
|
||||||
});
|
});
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
}, [pageId, ydoc, data?.token]);
|
}, [pageId, ydoc]);
|
||||||
|
|
||||||
const remoteProvider = useMemo(() => {
|
const remoteProvider = useMemo(() => {
|
||||||
const provider = new HocuspocusProvider({
|
const provider = new HocuspocusProvider({
|
||||||
name: documentName,
|
name: documentName,
|
||||||
url: collaborationURL,
|
url: collaborationURL,
|
||||||
document: ydoc,
|
document: ydoc,
|
||||||
token: data?.token,
|
token: collabQuery?.token,
|
||||||
connect: false,
|
connect: false,
|
||||||
preserveConnection: false,
|
preserveConnection: false,
|
||||||
|
onAuthenticationFailed: (auth: onAuthenticationFailedParameters) => {
|
||||||
|
collabRetryCount.current = collabRetryCount.current + 1;
|
||||||
|
refetchCollabToken().then(() => {
|
||||||
|
collabRetryCount.current = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (collabRetryCount.current > 20) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
onStatus: (status) => {
|
onStatus: (status) => {
|
||||||
if (status.status === "connected") {
|
if (status.status === "connected") {
|
||||||
setYjsConnectionStatus(status.status);
|
setYjsConnectionStatus(status.status);
|
||||||
|
|
@ -118,7 +130,7 @@ export default function PageEditor({
|
||||||
});
|
});
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
}, [ydoc, pageId, data?.token]);
|
}, [ydoc, pageId, collabQuery?.token]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
remoteProvider.connect();
|
remoteProvider.connect();
|
||||||
|
|
@ -253,23 +265,32 @@ export default function PageEditor({
|
||||||
documentState === "visible" &&
|
documentState === "visible" &&
|
||||||
remoteProvider?.status === WebSocketStatus.Disconnected
|
remoteProvider?.status === WebSocketStatus.Disconnected
|
||||||
) {
|
) {
|
||||||
remoteProvider.connect();
|
const reconnectTimeout = setTimeout(
|
||||||
resetIdle();
|
() => {
|
||||||
setTimeout(() => {
|
remoteProvider.connect();
|
||||||
setIsCollabReady(true);
|
resetIdle();
|
||||||
}, 600);
|
},
|
||||||
|
collabRetryCount.current > 2 ? 3000 : 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => clearTimeout(reconnectTimeout);
|
||||||
}
|
}
|
||||||
}, [isIdle, documentState, remoteProvider?.status]);
|
}, [isIdle, documentState, remoteProvider?.status]);
|
||||||
|
|
||||||
const isSynced = isLocalSynced && isRemoteSynced;
|
const isSynced = isLocalSynced && isRemoteSynced;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
const collabReadyTimeout = setTimeout(() => {
|
||||||
if (isSynced) {
|
if (
|
||||||
|
!isCollabReady &&
|
||||||
|
isSynced &&
|
||||||
|
remoteProvider.status === WebSocketStatus.Connected
|
||||||
|
) {
|
||||||
setIsCollabReady(true);
|
setIsCollabReady(true);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
}, [isRemoteSynced, isLocalSynced]);
|
return () => clearTimeout(collabReadyTimeout);
|
||||||
|
}, [isRemoteSynced, isLocalSynced, remoteProvider?.status]);
|
||||||
|
|
||||||
return isCollabReady ? (
|
return isCollabReady ? (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue