-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathuseYorkieDocument.ts
153 lines (133 loc) · 4.23 KB
/
useYorkieDocument.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import Color from "color";
import randomColor from "randomcolor";
import { useCallback, useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useBeforeUnload, useSearchParams } from "react-router-dom";
import * as yorkie from "yorkie-js-sdk";
import { selectAuth } from "../store/authSlice";
import { CodePairDocType } from "../store/editorSlice";
import { YorkieCodeMirrorDocType, YorkieCodeMirrorPresenceType } from "../utils/yorkie/yorkieSync";
import { useRefreshTokenMutation } from "./api/user";
import { selectUser } from "../store/userSlice";
const YORKIE_API_ADDR = import.meta.env.VITE_YORKIE_API_ADDR;
const YORKIE_API_KEY = import.meta.env.VITE_YORKIE_API_KEY;
yorkie.setLogLevel(4);
export const useYorkieDocument = (
yorkieDocumentId?: string | null,
presenceName?: string | null
) => {
const [searchParams] = useSearchParams();
const authStore = useSelector(selectAuth);
const userStore = useSelector(selectUser);
const [client, setClient] = useState<yorkie.Client | null>(null);
const [doc, setDoc] = useState<CodePairDocType | null>(null);
const { mutateAsync: mutateRefreshToken } = useRefreshTokenMutation();
const userID = userStore.data?.id || "";
const getYorkieToken = useCallback(
async (reason?: string) => {
const shareToken = searchParams.get("token");
let accessToken = authStore.accessToken;
const isShare = Boolean(shareToken);
if (reason) {
if (isShare) {
throw new Error("Cannot refresh token for shared documents");
} else {
try {
accessToken = await mutateRefreshToken();
} catch {
throw new Error("Failed to refresh token");
}
}
}
return isShare ? `share:${shareToken}` : `default:${accessToken}`;
},
[authStore.accessToken, mutateRefreshToken, searchParams]
);
const createYorkieClient = useCallback(async () => {
const syncLoopDuration = Number(searchParams.get("syncLoopDuration")) || 200;
const opts = {
apiKey: YORKIE_API_KEY,
authTokenInjector: getYorkieToken,
syncLoopDuration,
} as yorkie.ClientOptions;
if (userID) {
opts.metadata = { userID };
}
const newClient = new yorkie.Client(YORKIE_API_ADDR, opts);
await newClient.activate();
return newClient;
}, [getYorkieToken, searchParams, userID]);
const createYorkieDocument = useCallback(
(client: yorkie.Client, yorkieDocumentId: string, presenceName: string) => {
const newDocument = new yorkie.Document<
YorkieCodeMirrorDocType,
YorkieCodeMirrorPresenceType
>(yorkieDocumentId, { enableDevtools: false });
return client.attach(newDocument, {
initialPresence: {
name: presenceName,
color: Color(randomColor()).fade(0.15).toString(),
selection: null,
cursor: null,
},
});
},
[]
);
const cleanUpYorkieDocument = useCallback(async () => {
if (!client || !doc) return;
try {
await client.deactivate({ keepalive: true });
} catch (error) {
console.error("Error during Yorkie cleanup:", error);
}
}, [client, doc]);
useEffect(() => {
let mounted = true;
if (!yorkieDocumentId || !presenceName || doc || client) return;
const initializeYorkie = async () => {
try {
const newClient = await createYorkieClient();
const newDoc = await createYorkieDocument(
newClient,
yorkieDocumentId,
presenceName
);
// Clean up if the component is unmounted before the initialization is done
if (!mounted) {
await newClient.deactivate({ keepalive: true });
return;
}
setClient(newClient);
setDoc(newDoc);
// Expose the document to the window for debugging purposes
window.doc = newDoc;
} catch (error) {
console.error("Error initializing Yorkie: ", error);
}
};
initializeYorkie();
return () => {
mounted = false;
};
}, [
presenceName,
yorkieDocumentId,
doc,
client,
getYorkieToken,
createYorkieClient,
createYorkieDocument,
]);
// Clean up yorkie document on unmount
// For example, when the user navigates to a different page
useEffect(() => {
return () => {
cleanUpYorkieDocument();
};
}, [cleanUpYorkieDocument]);
// Clean up yorkie document on beforeunload
// For example, when the user closes the tab or refreshes the page
useBeforeUnload(cleanUpYorkieDocument);
return { client, doc };
};