removed firebase and added support for rooms without firebase and fix docker setup
This commit is contained in:
@ -97,6 +97,7 @@ import { AppFooter } from "./components/AppFooter";
|
||||
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
||||
import { useAtomWithInitialValue } from "../packages/excalidraw/jotai";
|
||||
import { appJotaiStore } from "./app-jotai";
|
||||
import { getStorageBackend } from "./data/config";
|
||||
|
||||
import "./index.scss";
|
||||
import { ResolutionType } from "../packages/excalidraw/utility-types";
|
||||
@ -354,11 +355,15 @@ const ExcalidrawWrapper = () => {
|
||||
}, [] as FileId[]) || [];
|
||||
|
||||
if (data.isExternalScene) {
|
||||
loadFilesFromFirebase(
|
||||
`${FIREBASE_STORAGE_PREFIXES.shareLinkFiles}/${data.id}`,
|
||||
data.key,
|
||||
fileIds,
|
||||
).then(({ loadedFiles, erroredFiles }) => {
|
||||
getStorageBackend()
|
||||
.then((storageBackend) => {
|
||||
return storageBackend.loadFilesFromStorageBackend(
|
||||
`${FIREBASE_STORAGE_PREFIXES.shareLinkFiles}/${data.id}`,
|
||||
data.key,
|
||||
fileIds,
|
||||
);
|
||||
})
|
||||
.then(({ loadedFiles, erroredFiles }) => {
|
||||
excalidrawAPI.addFiles(loadedFiles);
|
||||
updateStaleImageStatuses({
|
||||
excalidrawAPI,
|
||||
|
@ -83,6 +83,7 @@ import { atom, useAtom } from "jotai";
|
||||
import { appJotaiStore } from "../app-jotai";
|
||||
import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
|
||||
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||
import { getStorageBackend } from "../data/config";
|
||||
|
||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||
export const collabDialogShownAtom = atom(false);
|
||||
@ -140,7 +141,12 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
throw new AbortError();
|
||||
}
|
||||
|
||||
return loadFilesFromFirebase(`files/rooms/${roomId}`, roomKey, fileIds);
|
||||
const storageBackend = await getStorageBackend();
|
||||
return storageBackend.loadFilesFromStorageBackend(
|
||||
`files/rooms/${roomId}`,
|
||||
roomKey,
|
||||
fileIds,
|
||||
);
|
||||
},
|
||||
saveFiles: async ({ addedFiles }) => {
|
||||
const { roomId, roomKey } = this.portal;
|
||||
@ -148,7 +154,8 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
throw new AbortError();
|
||||
}
|
||||
|
||||
return saveFilesToFirebase({
|
||||
const storageBackend = await getStorageBackend();
|
||||
return storageBackend.saveFilesToStorageBackend({
|
||||
prefix: `${FIREBASE_STORAGE_PREFIXES.collabFiles}/${roomId}`,
|
||||
files: await encodeFilesForUpload({
|
||||
files: addedFiles,
|
||||
@ -267,11 +274,8 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
syncableElements: readonly SyncableExcalidrawElement[],
|
||||
) => {
|
||||
try {
|
||||
const savedData = await saveToFirebase(
|
||||
this.portal,
|
||||
syncableElements,
|
||||
this.excalidrawAPI.getAppState(),
|
||||
);
|
||||
const storageBackend = await getStorageBackend();
|
||||
const savedData = await storageBackend.saveToStorageBackend(this.portal, syncableElements, this.excalidrawAPI.getAppState());
|
||||
|
||||
if (this.isCollaborating() && savedData && savedData.reconciledElements) {
|
||||
this.handleRemoteSceneUpdate(
|
||||
@ -656,11 +660,12 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
this.excalidrawAPI.resetScene();
|
||||
|
||||
try {
|
||||
const elements = await loadFromFirebase(
|
||||
roomLinkData.roomId,
|
||||
roomLinkData.roomKey,
|
||||
this.portal.socket,
|
||||
);
|
||||
const storageBackend = await getStorageBackend();
|
||||
const elements = await storageBackend.loadFromStorageBackend(
|
||||
roomLinkData.roomId,
|
||||
roomLinkData.roomKey,
|
||||
this.portal.socket,
|
||||
);
|
||||
if (elements) {
|
||||
this.setLastBroadcastedOrReceivedSceneVersion(
|
||||
getSceneVersion(elements),
|
||||
|
@ -23,7 +23,7 @@ import type { Socket } from "socket.io-client";
|
||||
|
||||
class Portal {
|
||||
collab: TCollabClass;
|
||||
socket: Socket | null = null;
|
||||
socket: SocketIOClient.Socket | null = null;
|
||||
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
|
||||
roomId: string | null = null;
|
||||
roomKey: string | null = null;
|
||||
@ -33,7 +33,7 @@ class Portal {
|
||||
this.collab = collab;
|
||||
}
|
||||
|
||||
open(socket: Socket, id: string, key: string) {
|
||||
open(socket: SocketIOClient.Socket, id: string, key: string) {
|
||||
this.socket = socket;
|
||||
this.roomId = id;
|
||||
this.roomKey = key;
|
||||
|
45
excalidraw-app/data/StorageBackend.ts
Normal file
45
excalidraw-app/data/StorageBackend.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { SyncableExcalidrawElement } from ".";
|
||||
import { ExcalidrawElement, FileId } from "../../packages/excalidraw/element/types";
|
||||
import { AppState, BinaryFileData } from "../../packages/excalidraw/types";
|
||||
import Portal from "../collab/Portal";
|
||||
|
||||
export interface StorageBackend {
|
||||
isSaved: (portal: Portal, elements: readonly ExcalidrawElement[]) => boolean;
|
||||
saveToStorageBackend: (
|
||||
portal: Portal,
|
||||
elements: readonly SyncableExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => Promise<false | { reconciledElements: any }>;
|
||||
loadFromStorageBackend: (
|
||||
roomId: string,
|
||||
roomKey: string,
|
||||
socket: SocketIOClient.Socket | null,
|
||||
) => Promise<readonly ExcalidrawElement[] | null>;
|
||||
saveFilesToStorageBackend: ({
|
||||
prefix,
|
||||
files,
|
||||
}: {
|
||||
prefix: string;
|
||||
files: {
|
||||
id: FileId;
|
||||
buffer: Uint8Array;
|
||||
}[];
|
||||
}) => Promise<{
|
||||
savedFiles: Map<FileId, true>;
|
||||
erroredFiles: Map<FileId, true>;
|
||||
}>;
|
||||
loadFilesFromStorageBackend: (
|
||||
prefix: string,
|
||||
decryptionKey: string,
|
||||
filesIds: readonly FileId[],
|
||||
) => Promise<{
|
||||
loadedFiles: BinaryFileData[];
|
||||
erroredFiles: Map<FileId, true>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface StoredScene {
|
||||
sceneVersion: number;
|
||||
iv: Uint8Array;
|
||||
ciphertext: ArrayBuffer;
|
||||
}
|
54
excalidraw-app/data/config.ts
Normal file
54
excalidraw-app/data/config.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import {
|
||||
isSavedToFirebase,
|
||||
loadFilesFromFirebase,
|
||||
loadFromFirebase,
|
||||
saveFilesToFirebase,
|
||||
saveToFirebase,
|
||||
} from "./firebase";
|
||||
import {
|
||||
isSavedToHttpStorage,
|
||||
loadFilesFromHttpStorage,
|
||||
loadFromHttpStorage,
|
||||
saveFilesToHttpStorage,
|
||||
saveToHttpStorage,
|
||||
} from "./httpStorage";
|
||||
import { StorageBackend } from "./StorageBackend";
|
||||
|
||||
const firebaseStorage: StorageBackend = {
|
||||
isSaved: isSavedToFirebase,
|
||||
saveToStorageBackend: saveToFirebase,
|
||||
loadFromStorageBackend: loadFromFirebase,
|
||||
saveFilesToStorageBackend: saveFilesToFirebase,
|
||||
loadFilesFromStorageBackend: loadFilesFromFirebase,
|
||||
};
|
||||
|
||||
const httpStorage: StorageBackend = {
|
||||
isSaved: isSavedToHttpStorage,
|
||||
saveToStorageBackend: saveToHttpStorage,
|
||||
loadFromStorageBackend: loadFromHttpStorage,
|
||||
saveFilesToStorageBackend: saveFilesToHttpStorage,
|
||||
loadFilesFromStorageBackend: loadFilesFromHttpStorage,
|
||||
};
|
||||
|
||||
const storageBackends = new Map<string, StorageBackend>()
|
||||
.set("firebase", firebaseStorage)
|
||||
.set("http", httpStorage);
|
||||
|
||||
export let storageBackend: StorageBackend | null = null;
|
||||
|
||||
export async function getStorageBackend() {
|
||||
if (storageBackend) {
|
||||
return storageBackend;
|
||||
}
|
||||
|
||||
const storageBackendName = import.meta.env.VITE_APP_STORAGE_BACKEND || '';
|
||||
|
||||
if (storageBackends.has(storageBackendName)) {
|
||||
storageBackend = storageBackends.get(storageBackendName) as StorageBackend;
|
||||
} else {
|
||||
console.warn("No storage backend found, default to firebase");
|
||||
storageBackend = firebaseStorage;
|
||||
}
|
||||
|
||||
return storageBackend;
|
||||
}
|
@ -21,7 +21,7 @@ import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
||||
import { reconcileElements } from "../collab/reconciliation";
|
||||
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
||||
import { ResolutionType } from "../../packages/excalidraw/utility-types";
|
||||
import type { Socket } from "socket.io-client";
|
||||
import { Socket } from "socket.io-client";
|
||||
|
||||
// private
|
||||
// -----------------------------------------------------------------------------
|
||||
@ -49,8 +49,10 @@ const _loadFirebase = async () => {
|
||||
const firebase = (
|
||||
await import(/* webpackChunkName: "firebase" */ "firebase/app")
|
||||
).default;
|
||||
const storage = import.meta.env.VITE_APP_STORAGE_BACKEND;
|
||||
const useFirebase = storage === "firebase";
|
||||
|
||||
if (!isFirebaseInitialized) {
|
||||
if (useFirebase && !isFirebaseInitialized) {
|
||||
try {
|
||||
firebase.initializeApp(FIREBASE_CONFIG);
|
||||
} catch (error: any) {
|
||||
@ -139,12 +141,12 @@ const decryptElements = async (
|
||||
};
|
||||
|
||||
class FirebaseSceneVersionCache {
|
||||
private static cache = new WeakMap<Socket, number>();
|
||||
static get = (socket: Socket) => {
|
||||
private static cache = new WeakMap<SocketIOClient.Socket, number>();
|
||||
static get = (socket: SocketIOClient.Socket) => {
|
||||
return FirebaseSceneVersionCache.cache.get(socket);
|
||||
};
|
||||
static set = (
|
||||
socket: Socket,
|
||||
socket: SocketIOClient.Socket,
|
||||
elements: readonly SyncableExcalidrawElement[],
|
||||
) => {
|
||||
FirebaseSceneVersionCache.cache.set(socket, getSceneVersion(elements));
|
||||
@ -286,7 +288,7 @@ export const saveToFirebase = async (
|
||||
export const loadFromFirebase = async (
|
||||
roomId: string,
|
||||
roomKey: string,
|
||||
socket: Socket | null,
|
||||
socket: SocketIOClient.Socket | null,
|
||||
): Promise<readonly ExcalidrawElement[] | null> => {
|
||||
const firebase = await loadFirestore();
|
||||
const db = firebase.firestore();
|
||||
|
266
excalidraw-app/data/httpStorage.ts
Normal file
266
excalidraw-app/data/httpStorage.ts
Normal file
@ -0,0 +1,266 @@
|
||||
// Inspired and partly copied from https://gitlab.com/kiliandeca/excalidraw-fork
|
||||
// MIT, Kilian Decaderincourt
|
||||
|
||||
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
||||
import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
||||
import { decompressData } from "../../packages/excalidraw/data/encode";
|
||||
import { encryptData, decryptData, IV_LENGTH_BYTES } from "../../packages/excalidraw/data/encryption";
|
||||
import { restoreElements } from "../../packages/excalidraw/data/restore";
|
||||
import { getSceneVersion } from "../../packages/excalidraw/element";
|
||||
import { ExcalidrawElement, FileId } from "../../packages/excalidraw/element/types";
|
||||
import {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFileMetadata,
|
||||
DataURL,
|
||||
} from "../../packages/excalidraw/types";
|
||||
import Portal from "../collab/Portal";
|
||||
import { reconcileElements } from "../collab/reconciliation";
|
||||
import { StoredScene } from "./StorageBackend";
|
||||
|
||||
const HTTP_STORAGE_BACKEND_URL = import.meta.env.VITE_APP_HTTP_STORAGE_BACKEND_URL;
|
||||
const SCENE_VERSION_LENGTH_BYTES = 4
|
||||
|
||||
// There is a lot of intentional duplication with the firebase file
|
||||
// to prevent modifying upstream files and ease futur maintenance of this fork
|
||||
|
||||
const httpStorageSceneVersionCache = new WeakMap<
|
||||
SocketIOClient.Socket,
|
||||
number
|
||||
>();
|
||||
|
||||
export const isSavedToHttpStorage = (
|
||||
portal: Portal,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
): boolean => {
|
||||
if (portal.socket && portal.roomId && portal.roomKey) {
|
||||
const sceneVersion = getSceneVersion(elements);
|
||||
|
||||
return httpStorageSceneVersionCache.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 const saveToHttpStorage = async (
|
||||
portal: Portal,
|
||||
elements: readonly SyncableExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => {
|
||||
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 ||
|
||||
isSavedToHttpStorage(portal, elements)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sceneVersion = getSceneVersion(elements);
|
||||
const getResponse = await fetch(
|
||||
`${HTTP_STORAGE_BACKEND_URL}/rooms/${roomId}`,
|
||||
);
|
||||
|
||||
if (!getResponse.ok && getResponse.status !== 404) {
|
||||
return false;
|
||||
};
|
||||
|
||||
if(getResponse.status === 404) {
|
||||
const result: boolean = await saveElementsToBackend(roomKey, roomId, [...elements], sceneVersion)
|
||||
if(result) {
|
||||
return {
|
||||
reconciledElements: null
|
||||
}
|
||||
}
|
||||
return false
|
||||
};
|
||||
|
||||
// If room already exist, we compare scene versions to check
|
||||
// if we're up to date before saving our scene
|
||||
const buffer = await getResponse.arrayBuffer();
|
||||
const sceneVersionFromRequest = parseSceneVersionFromRequest(buffer);
|
||||
if (sceneVersionFromRequest >= sceneVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const existingElements = await getElementsFromBuffer(buffer, roomKey);
|
||||
const reconciledElements = getSyncableElements(
|
||||
reconcileElements(elements, existingElements, appState),
|
||||
);
|
||||
|
||||
const result: boolean = await saveElementsToBackend(roomKey, roomId, reconciledElements, sceneVersion)
|
||||
|
||||
if (result) {
|
||||
httpStorageSceneVersionCache.set(socket, sceneVersion);
|
||||
return {
|
||||
reconciledElements: elements
|
||||
};
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const loadFromHttpStorage = async (
|
||||
roomId: string,
|
||||
roomKey: string,
|
||||
socket: SocketIOClient.Socket | null,
|
||||
): Promise<readonly ExcalidrawElement[] | null> => {
|
||||
const HTTP_STORAGE_BACKEND_URL = import.meta.env.VITE_APP_HTTP_STORAGE_BACKEND_URL;
|
||||
const getResponse = await fetch(
|
||||
`${HTTP_STORAGE_BACKEND_URL}/rooms/${roomId}`,
|
||||
);
|
||||
|
||||
const buffer = await getResponse.arrayBuffer();
|
||||
const elements = await getElementsFromBuffer(buffer, roomKey);
|
||||
|
||||
if (socket) {
|
||||
httpStorageSceneVersionCache.set(socket, getSceneVersion(elements));
|
||||
}
|
||||
|
||||
return restoreElements(elements, null);
|
||||
};
|
||||
|
||||
const getElementsFromBuffer = async (
|
||||
buffer: ArrayBuffer,
|
||||
key: string,
|
||||
): Promise<readonly ExcalidrawElement[]> => {
|
||||
// Buffer should contain both the IV (fixed length) and encrypted data
|
||||
const sceneVersion = parseSceneVersionFromRequest(buffer);
|
||||
const iv = new Uint8Array(buffer.slice(SCENE_VERSION_LENGTH_BYTES, IV_LENGTH_BYTES + SCENE_VERSION_LENGTH_BYTES));
|
||||
const encrypted = buffer.slice(IV_LENGTH_BYTES + SCENE_VERSION_LENGTH_BYTES, buffer.byteLength);
|
||||
|
||||
return await decryptElements(
|
||||
{ sceneVersion: sceneVersion, ciphertext: encrypted, iv },
|
||||
key
|
||||
);
|
||||
};
|
||||
|
||||
export const saveFilesToHttpStorage = async ({
|
||||
prefix,
|
||||
files,
|
||||
}: {
|
||||
prefix: string;
|
||||
files: { id: FileId; buffer: Uint8Array }[];
|
||||
}) => {
|
||||
const erroredFiles = new Map<FileId, true>();
|
||||
const savedFiles = new Map<FileId, true>();
|
||||
|
||||
const HTTP_STORAGE_BACKEND_URL = import.meta.env.VITE_APP_HTTP_STORAGE_BACKEND_URL;
|
||||
|
||||
await Promise.all(
|
||||
files.map(async ({ id, buffer }) => {
|
||||
try {
|
||||
const payloadBlob = new Blob([buffer]);
|
||||
const payload = await new Response(payloadBlob).arrayBuffer();
|
||||
await fetch(`${HTTP_STORAGE_BACKEND_URL}/files/${id}`, {
|
||||
method: "PUT",
|
||||
body: payload,
|
||||
});
|
||||
savedFiles.set(id, true);
|
||||
} catch (error: any) {
|
||||
erroredFiles.set(id, true);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return { savedFiles, erroredFiles };
|
||||
};
|
||||
|
||||
export const loadFilesFromHttpStorage = async (
|
||||
prefix: string,
|
||||
decryptionKey: string,
|
||||
filesIds: readonly FileId[],
|
||||
) => {
|
||||
const loadedFiles: BinaryFileData[] = [];
|
||||
const erroredFiles = new Map<FileId, true>();
|
||||
|
||||
//////////////
|
||||
await Promise.all(
|
||||
[...new Set(filesIds)].map(async (id) => {
|
||||
try {
|
||||
const HTTP_STORAGE_BACKEND_URL = import.meta.env.VITE_APP_HTTP_STORAGE_BACKEND_URL;
|
||||
const response = await fetch(`${HTTP_STORAGE_BACKEND_URL}/files/${id}`);
|
||||
if (response.status < 400) {
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
|
||||
const { data, metadata } = await decompressData<BinaryFileMetadata>(
|
||||
new Uint8Array(arrayBuffer),
|
||||
{
|
||||
decryptionKey,
|
||||
},
|
||||
);
|
||||
|
||||
const dataURL = new TextDecoder().decode(data) as DataURL;
|
||||
|
||||
loadedFiles.push({
|
||||
mimeType: metadata.mimeType || MIME_TYPES.binary,
|
||||
id,
|
||||
dataURL,
|
||||
created: metadata?.created || Date.now(),
|
||||
});
|
||||
} else {
|
||||
erroredFiles.set(id, true);
|
||||
}
|
||||
} catch (error: any) {
|
||||
erroredFiles.set(id, true);
|
||||
console.error(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
//////
|
||||
|
||||
return { loadedFiles, erroredFiles };
|
||||
};
|
||||
|
||||
const saveElementsToBackend = async (roomKey: string, roomId: string, elements: SyncableExcalidrawElement[], sceneVersion: number) => {
|
||||
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
||||
|
||||
// Concatenate Scene Version, IV with encrypted data (IV does not have to be secret).
|
||||
const numberBuffer = new ArrayBuffer(4);
|
||||
const numberView = new DataView(numberBuffer);
|
||||
numberView.setUint32(0, sceneVersion, false);
|
||||
const sceneVersionBuffer = numberView.buffer;
|
||||
const payloadBlob = await new Response(new Blob([sceneVersionBuffer, iv.buffer, ciphertext])).arrayBuffer();
|
||||
const putResponse = await fetch(
|
||||
`${HTTP_STORAGE_BACKEND_URL}/rooms/${roomId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: payloadBlob,
|
||||
},
|
||||
);
|
||||
|
||||
return putResponse.ok
|
||||
}
|
||||
|
||||
const parseSceneVersionFromRequest = (buffer: ArrayBuffer) => {
|
||||
const view = new DataView(buffer);
|
||||
return view.getUint32(0, false);
|
||||
}
|
||||
|
||||
const decryptElements = async (
|
||||
data: StoredScene,
|
||||
roomKey: string,
|
||||
): Promise<readonly ExcalidrawElement[]> => {
|
||||
const ciphertext = data.ciphertext;
|
||||
const iv = data.iv;
|
||||
|
||||
const decrypted = await decryptData(iv, ciphertext, roomKey);
|
||||
const decodedData = new TextDecoder("utf-8").decode(
|
||||
new Uint8Array(decrypted),
|
||||
);
|
||||
return JSON.parse(decodedData);
|
||||
};
|
||||
|
||||
const encryptElements = async (
|
||||
key: string,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
): Promise<{ ciphertext: ArrayBuffer; iv: Uint8Array }> => {
|
||||
const json = JSON.stringify(elements);
|
||||
const encoded = new TextEncoder().encode(json);
|
||||
const { encryptedBuffer, iv } = await encryptData(key, encoded);
|
||||
|
||||
return { ciphertext: encryptedBuffer, iv };
|
||||
};
|
@ -4,6 +4,7 @@ import {
|
||||
} from "../../packages/excalidraw/data/encode";
|
||||
import {
|
||||
decryptData,
|
||||
encryptData,
|
||||
generateEncryptionKey,
|
||||
IV_LENGTH_BYTES,
|
||||
} from "../../packages/excalidraw/data/encryption";
|
||||
@ -33,7 +34,7 @@ import {
|
||||
WS_SUBTYPES,
|
||||
} from "../app_constants";
|
||||
import { encodeFilesForUpload } from "./FileManager";
|
||||
import { saveFilesToFirebase } from "./firebase";
|
||||
import { getStorageBackend } from "./config";
|
||||
|
||||
export type SyncableExcalidrawElement = ExcalidrawElement & {
|
||||
_brand: "SyncableExcalidrawElement";
|
||||
@ -343,7 +344,8 @@ export const exportToBackend = async (
|
||||
url.hash = `json=${json.id},${encryptionKey}`;
|
||||
const urlString = url.toString();
|
||||
|
||||
await saveFilesToFirebase({
|
||||
const storageBackend = await getStorageBackend();
|
||||
await storageBackend.saveFilesToStorageBackend({
|
||||
prefix: `/files/shareLinks/${json.id}`,
|
||||
files: filesToUpload,
|
||||
});
|
||||
|
@ -30,7 +30,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-node": "node ./scripts/build-node.js",
|
||||
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true VITE_APP_DISABLE_TRACKING=true vite build",
|
||||
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true VITE_APP_DISABLE_TRACKING=true VITE_APP_ENABLE_ESLINT=false vite build",
|
||||
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build",
|
||||
"build:version": "node ../scripts/build-version.js",
|
||||
"build": "yarn build:app && yarn build:version",
|
||||
|
3
excalidraw-app/vite-env.d.ts
vendored
3
excalidraw-app/vite-env.d.ts
vendored
@ -35,6 +35,9 @@ interface ImportMetaEnv {
|
||||
|
||||
VITE_APP_GIT_SHA: string;
|
||||
|
||||
VITE_APP_HTTP_STORAGE_BACKEND_URL: string;
|
||||
VITE_APP_STORAGE_BACKEND: "http" | "firebase";
|
||||
|
||||
MODE: string;
|
||||
|
||||
DEV: string;
|
||||
|
@ -10,9 +10,8 @@ const envVars = loadEnv("", `../`);
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: Number(envVars.VITE_APP_PORT || 3000),
|
||||
// open the browser
|
||||
open: true,
|
||||
host: '0.0.0.0',
|
||||
port: Number(envVars.VITE_APP_PORT || 3000)
|
||||
},
|
||||
// We need to specify the envDir since now there are no
|
||||
//more located in parallel with the vite.config.ts file but in parent dir
|
||||
|
Reference in New Issue
Block a user