integrate deletion to collab sessions

This commit is contained in:
Ryan Di 2025-06-02 14:50:39 +10:00
parent f71c200106
commit 08a39e2034
8 changed files with 90 additions and 8 deletions

View File

@ -23,6 +23,7 @@ export enum WS_SUBTYPES {
INVALID_RESPONSE = "INVALID_RESPONSE", INVALID_RESPONSE = "INVALID_RESPONSE",
INIT = "SCENE_INIT", INIT = "SCENE_INIT",
UPDATE = "SCENE_UPDATE", UPDATE = "SCENE_UPDATE",
DELETE = "SCENE_DELETE",
MOUSE_LOCATION = "MOUSE_LOCATION", MOUSE_LOCATION = "MOUSE_LOCATION",
IDLE_STATUS = "IDLE_STATUS", IDLE_STATUS = "IDLE_STATUS",
USER_VISIBLE_SCENE_BOUNDS = "USER_VISIBLE_SCENE_BOUNDS", USER_VISIBLE_SCENE_BOUNDS = "USER_VISIBLE_SCENE_BOUNDS",

View File

@ -72,6 +72,7 @@ import {
} from "../data/FileManager"; } from "../data/FileManager";
import { LocalData } from "../data/LocalData"; import { LocalData } from "../data/LocalData";
import { import {
deleteRoomFromFirebase,
isSavedToFirebase, isSavedToFirebase,
loadFilesFromFirebase, loadFilesFromFirebase,
loadFromFirebase, loadFromFirebase,
@ -115,6 +116,7 @@ export interface CollabAPI {
onPointerUpdate: CollabInstance["onPointerUpdate"]; onPointerUpdate: CollabInstance["onPointerUpdate"];
startCollaboration: CollabInstance["startCollaboration"]; startCollaboration: CollabInstance["startCollaboration"];
stopCollaboration: CollabInstance["stopCollaboration"]; stopCollaboration: CollabInstance["stopCollaboration"];
deleteRoom: CollabInstance["deleteRoom"];
syncElements: CollabInstance["syncElements"]; syncElements: CollabInstance["syncElements"];
fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"]; fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
setUsername: CollabInstance["setUsername"]; setUsername: CollabInstance["setUsername"];
@ -228,6 +230,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
isCollaborating: this.isCollaborating, isCollaborating: this.isCollaborating,
onPointerUpdate: this.onPointerUpdate, onPointerUpdate: this.onPointerUpdate,
startCollaboration: this.startCollaboration, startCollaboration: this.startCollaboration,
deleteRoom: this.deleteRoom,
syncElements: this.syncElements, syncElements: this.syncElements,
fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase, fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase,
stopCollaboration: this.stopCollaboration, stopCollaboration: this.stopCollaboration,
@ -675,6 +678,18 @@ class Collab extends PureComponent<CollabProps, CollabState> {
break; break;
} }
case WS_SUBTYPES.DELETE: {
const { roomId } = decryptedData.payload;
if (this.portal.roomId === roomId) {
this.destroySocketClient({ isUnload: true });
this.setIsCollaborating(false);
this.setActiveRoomLink(null);
this.setErrorDialog(t("alerts.collabRoomDeleted"));
window.history.pushState({}, APP_NAME, window.location.origin);
}
break;
}
default: { default: {
assertNever(decryptedData, null); assertNever(decryptedData, null);
} }
@ -894,6 +909,42 @@ class Collab extends PureComponent<CollabProps, CollabState> {
}); });
}; };
deleteRoom = async (): Promise<void> => {
if (!this.portal.socket || !this.portal.roomId) {
return;
}
const { roomId, roomKey } = this.portal;
if (!roomId || !roomKey) {
return;
}
const link = this.getActiveRoomLink();
if (!link) {
return;
}
// check if the room belongs to the current user
const isOwner = await roomManager.isRoomOwnedByUser(link);
if (!isOwner) {
return;
}
try {
this.portal.broadcastRoomDeletion();
await deleteRoomFromFirebase(roomId, roomKey);
await roomManager.deleteRoom(roomId);
this.stopCollaboration(false);
this.setActiveRoomLink(null);
window.history.pushState({}, APP_NAME, window.location.origin);
} catch (error) {
console.error("Failed to delete room:", error);
this.setErrorDialog(t("errors.roomDeletionFailed"));
throw error;
}
};
public setLastBroadcastedOrReceivedSceneVersion = (version: number) => { public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
this.lastBroadcastedOrReceivedSceneVersion = version; this.lastBroadcastedOrReceivedSceneVersion = version;
}; };

View File

@ -252,6 +252,20 @@ class Portal {
this.socket.emit(WS_EVENTS.USER_FOLLOW_CHANGE, payload); this.socket.emit(WS_EVENTS.USER_FOLLOW_CHANGE, payload);
} }
}; };
broadcastRoomDeletion = async () => {
if (this.socket?.id) {
const data: SocketUpdateDataSource["ROOM_DELETED"] = {
type: WS_SUBTYPES.DELETE,
payload: {
socketId: this.socket.id as SocketId,
roomId: this.roomId!,
},
};
this._broadcastSocketData(data as SocketUpdateData);
}
};
} }
export default Portal; export default Portal;

View File

@ -315,3 +315,10 @@ export const loadFilesFromFirebase = async (
return { loadedFiles, erroredFiles }; return { loadedFiles, erroredFiles };
}; };
export const deleteRoomFromFirebase = async (
roomId: string,
roomKey: string,
): Promise<void> => {
// TODO: delete the room...
};

View File

@ -119,6 +119,13 @@ export type SocketUpdateDataSource = {
username: string; username: string;
}; };
}; };
ROOM_DELETED: {
type: WS_SUBTYPES.DELETE;
payload: {
socketId: SocketId;
roomId: string;
};
};
}; };
export type SocketUpdateDataIncoming = export type SocketUpdateDataIncoming =
@ -310,7 +317,7 @@ export const exportToBackend = async (
const response = await fetch(BACKEND_V2_POST, { const response = await fetch(BACKEND_V2_POST, {
method: "POST", method: "POST",
body: payload.buffer, body: new Uint8Array(payload.buffer),
}); });
const json = await response.json(); const json = await response.json();
if (json.id) { if (json.id) {

View File

@ -186,7 +186,7 @@ class RoomManager {
return false; return false;
} }
const [, roomId] = match; const roomId = match[1];
return rooms.some((room) => room.roomId === roomId); return rooms.some((room) => room.roomId === roomId);
} catch (error) { } catch (error) {
console.warn("Failed to check room ownership:", error); console.warn("Failed to check room ownership:", error);

View File

@ -198,10 +198,10 @@ const ActiveRoomDialog = ({
color="danger" color="danger"
onClick={() => { onClick={() => {
trackEvent("share", "room deleted"); trackEvent("share", "room deleted");
// TODO: handle deletion collabAPI.deleteRoom();
// 1. stop collaboration (for all users?) if (!collabAPI.isCollaborating()) {
// 2. delete room from backend (firebase) handleClose();
// 3. close }
}} }}
/> />
)} )}

View File

@ -253,7 +253,8 @@
"resetLibrary": "This will clear your library. Are you sure?", "resetLibrary": "This will clear your library. Are you sure?",
"removeItemsFromsLibrary": "Delete {{count}} item(s) from library?", "removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
"invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.", "invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.",
"collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!" "collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!",
"collabRoomDeleted": "This collab room has been deleted by its owner."
}, },
"errors": { "errors": {
"unsupportedFileType": "Unsupported file type.", "unsupportedFileType": "Unsupported file type.",
@ -280,7 +281,8 @@
}, },
"asyncPasteFailedOnRead": "Couldn't paste (couldn't read from system clipboard).", "asyncPasteFailedOnRead": "Couldn't paste (couldn't read from system clipboard).",
"asyncPasteFailedOnParse": "Couldn't paste.", "asyncPasteFailedOnParse": "Couldn't paste.",
"copyToSystemClipboardFailed": "Couldn't copy to clipboard." "copyToSystemClipboardFailed": "Couldn't copy to clipboard.",
"roomDeletionFailed": "Couldn't delete the collaboration room."
}, },
"toolBar": { "toolBar": {
"selection": "Selection", "selection": "Selection",