save room to firebase on unload or portal close (#2207)
* save on unload or portal close * align naming
This commit is contained in:
parent
ae1ab1ab37
commit
d18a72c879
@ -176,7 +176,11 @@ import {
|
||||
import { MaybeTransformHandleType } from "../element/transformHandles";
|
||||
import { renderSpreadsheet } from "../charts";
|
||||
import { isValidLibrary } from "../data/json";
|
||||
import { loadFromFirebase, saveToFirebase } from "../data/firebase";
|
||||
import {
|
||||
loadFromFirebase,
|
||||
saveToFirebase,
|
||||
isSavedToFirebase,
|
||||
} from "../data/firebase";
|
||||
|
||||
/**
|
||||
* @param func handler taking at most single parameter (event).
|
||||
@ -469,7 +473,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
return false;
|
||||
}
|
||||
|
||||
const roomId = roomMatch[1];
|
||||
const roomID = roomMatch[1];
|
||||
|
||||
let collabForceLoadFlag;
|
||||
try {
|
||||
@ -488,7 +492,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
);
|
||||
// if loading same room as the one previously unloaded within 15sec
|
||||
// force reload without prompting
|
||||
if (previousRoom === roomId && Date.now() - timestamp < 15000) {
|
||||
if (previousRoom === roomID && Date.now() - timestamp < 15000) {
|
||||
return true;
|
||||
}
|
||||
} catch {}
|
||||
@ -784,7 +788,17 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
if (this.state.isCollaborating && this.scene.getElements().length > 0) {
|
||||
const syncableElements = getSyncableElements(
|
||||
this.scene.getElementsIncludingDeleted(),
|
||||
);
|
||||
if (
|
||||
this.state.isCollaborating &&
|
||||
!isSavedToFirebase(this.portal, syncableElements)
|
||||
) {
|
||||
// this won't run in time if user decides to leave the site, but
|
||||
// the purpose is to run in immediately after user decides to stay
|
||||
this.saveCollabRoomToFirebase(syncableElements);
|
||||
|
||||
event.preventDefault();
|
||||
// NOTE: modern browsers no longer allow showing a custom message here
|
||||
event.returnValue = "";
|
||||
@ -1182,6 +1196,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
};
|
||||
|
||||
closePortal = () => {
|
||||
this.saveCollabRoomToFirebase();
|
||||
window.history.pushState({}, "Excalidraw", window.location.origin);
|
||||
this.destroySocketClient();
|
||||
};
|
||||
@ -1227,8 +1242,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
}
|
||||
const roomMatch = getCollaborationLinkData(window.location.href);
|
||||
if (roomMatch) {
|
||||
const roomId = roomMatch[1];
|
||||
const roomSecret = roomMatch[2];
|
||||
const roomID = roomMatch[1];
|
||||
const roomKey = roomMatch[2];
|
||||
|
||||
const initialize = () => {
|
||||
this.portal.socketInitialized = true;
|
||||
@ -1358,7 +1373,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
/* webpackChunkName: "socketIoClient" */ "socket.io-client"
|
||||
);
|
||||
|
||||
this.portal.open(socketIOClient(SOCKET_SERVER), roomId, roomSecret);
|
||||
this.portal.open(socketIOClient(SOCKET_SERVER), roomID, roomKey);
|
||||
|
||||
// All socket listeners are moving to Portal
|
||||
this.portal.socket!.on(
|
||||
@ -1430,7 +1445,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
});
|
||||
|
||||
try {
|
||||
const elements = await loadFromFirebase(roomId, roomSecret);
|
||||
const elements = await loadFromFirebase(roomID, roomKey);
|
||||
if (elements) {
|
||||
updateScene(
|
||||
{ type: "SCENE_UPDATE", payload: { elements } },
|
||||
@ -1484,6 +1499,18 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
}
|
||||
};
|
||||
|
||||
saveCollabRoomToFirebase = async (
|
||||
syncableElements: ExcalidrawElement[] = getSyncableElements(
|
||||
this.scene.getElementsIncludingDeleted(),
|
||||
),
|
||||
) => {
|
||||
try {
|
||||
await saveToFirebase(this.portal, syncableElements);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// maybe should move to Portal
|
||||
broadcastScene = async (
|
||||
sceneType: SCENE.INIT | SCENE.UPDATE,
|
||||
@ -1530,16 +1557,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
data as SocketUpdateData,
|
||||
);
|
||||
|
||||
if (syncAll && this.portal.roomID && this.portal.roomKey) {
|
||||
if (syncAll && this.state.isCollaborating) {
|
||||
await Promise.all([
|
||||
broadcastPromise,
|
||||
saveToFirebase(
|
||||
this.portal.roomID,
|
||||
this.portal.roomKey,
|
||||
syncableElements,
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
}),
|
||||
this.saveCollabRoomToFirebase(syncableElements),
|
||||
]);
|
||||
} else {
|
||||
await broadcastPromise;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createIV, getImportedKey } from "./index";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { getSceneVersion } from "../element";
|
||||
import Portal from "../components/Portal";
|
||||
|
||||
let firebasePromise: Promise<typeof import("firebase/app")> | null = null;
|
||||
|
||||
@ -69,14 +70,40 @@ async function decryptElements(
|
||||
return JSON.parse(decodedData);
|
||||
}
|
||||
|
||||
const firebaseSceneVersionCache = new WeakMap<SocketIOClient.Socket, number>();
|
||||
|
||||
export const isSavedToFirebase = (
|
||||
portal: Portal,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
): boolean => {
|
||||
if (portal.socket && portal.roomID && portal.roomKey) {
|
||||
const sceneVersion = getSceneVersion(elements);
|
||||
return firebaseSceneVersionCache.get(portal.socket) === sceneVersion;
|
||||
}
|
||||
// if no room exists, consider the room saved so that we don't unnecessarily
|
||||
// prevent unload (there's nothing we could do at that point anyway)
|
||||
return true;
|
||||
};
|
||||
|
||||
export async function saveToFirebase(
|
||||
roomId: string,
|
||||
roomSecret: string,
|
||||
portal: Portal,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
) {
|
||||
const { roomID, roomKey, socket } = portal;
|
||||
if (
|
||||
// if no room exists, consider the room saved because there's nothing we can
|
||||
// do at this point
|
||||
!roomID ||
|
||||
!roomKey ||
|
||||
!socket ||
|
||||
isSavedToFirebase(portal, elements)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const firebase = await getFirebase();
|
||||
const sceneVersion = getSceneVersion(elements);
|
||||
const { ciphertext, iv } = await encryptElements(roomSecret, elements);
|
||||
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
||||
|
||||
const nextDocData = {
|
||||
sceneVersion,
|
||||
@ -87,7 +114,7 @@ export async function saveToFirebase(
|
||||
} as FirebaseStoredScene;
|
||||
|
||||
const db = firebase.firestore();
|
||||
const docRef = db.collection("scenes").doc(roomId);
|
||||
const docRef = db.collection("scenes").doc(roomID);
|
||||
const didUpdate = await db.runTransaction(async (transaction) => {
|
||||
const doc = await transaction.get(docRef);
|
||||
if (!doc.exists) {
|
||||
@ -104,17 +131,21 @@ export async function saveToFirebase(
|
||||
return true;
|
||||
});
|
||||
|
||||
if (didUpdate) {
|
||||
firebaseSceneVersionCache.set(socket, sceneVersion);
|
||||
}
|
||||
|
||||
return didUpdate;
|
||||
}
|
||||
|
||||
export async function loadFromFirebase(
|
||||
roomId: string,
|
||||
roomSecret: string,
|
||||
roomID: string,
|
||||
roomKey: string,
|
||||
): Promise<readonly ExcalidrawElement[] | null> {
|
||||
const firebase = await getFirebase();
|
||||
const db = firebase.firestore();
|
||||
|
||||
const docRef = db.collection("scenes").doc(roomId);
|
||||
const docRef = db.collection("scenes").doc(roomID);
|
||||
const doc = await docRef.get();
|
||||
if (!doc.exists) {
|
||||
return null;
|
||||
@ -122,6 +153,6 @@ export async function loadFromFirebase(
|
||||
const storedScene = doc.data() as FirebaseStoredScene;
|
||||
const ciphertext = storedScene.ciphertext.toUint8Array();
|
||||
const iv = storedScene.iv.toUint8Array();
|
||||
const plaintext = await decryptElements(roomSecret, iv, ciphertext);
|
||||
const plaintext = await decryptElements(roomKey, iv, ciphertext);
|
||||
return plaintext;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user