removed firebase and added support for rooms without firebase and fix docker setup
This commit is contained in:
parent
d19b51d4f8
commit
ad571b12b8
@ -4,6 +4,7 @@
|
|||||||
!.eslintrc.json
|
!.eslintrc.json
|
||||||
!.npmrc
|
!.npmrc
|
||||||
!.prettierrc
|
!.prettierrc
|
||||||
|
!excalidraw-app/
|
||||||
!package.json
|
!package.json
|
||||||
!public/
|
!public/
|
||||||
!packages/
|
!packages/
|
||||||
|
1
.env.default
Normal file
1
.env.default
Normal file
@ -0,0 +1 @@
|
|||||||
|
REDIS_PASSWORD=CHANGE_ME
|
@ -1,11 +1,14 @@
|
|||||||
VITE_APP_BACKEND_V2_GET_URL=https://json-dev.excalidraw.com/api/v2/
|
VITE_APP_BACKEND_V2_GET_URL=http://localhost:8080/api/v2/scenes/
|
||||||
VITE_APP_BACKEND_V2_POST_URL=https://json-dev.excalidraw.com/api/v2/post/
|
VITE_APP_BACKEND_V2_POST_URL=http://localhost:8080/api/v2/scenes/
|
||||||
|
|
||||||
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
|
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
|
||||||
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
|
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
|
||||||
|
|
||||||
|
VITE_APP_STORAGE_BACKEND=http
|
||||||
|
VITE_APP_HTTP_STORAGE_BACKEND_URL=http://localhost:8080/api/v2
|
||||||
|
|
||||||
# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
|
# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
|
||||||
VITE_APP_WS_SERVER_URL=http://localhost:3002
|
VITE_APP_WS_SERVER_URL=http://localhost:5001
|
||||||
|
|
||||||
# set this only if using the collaboration workflow we use on excalidraw.com
|
# set this only if using the collaboration workflow we use on excalidraw.com
|
||||||
VITE_APP_PORTAL_URL=
|
VITE_APP_PORTAL_URL=
|
||||||
@ -13,9 +16,9 @@ VITE_APP_PORTAL_URL=
|
|||||||
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
||||||
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
||||||
|
|
||||||
VITE_APP_AI_BACKEND=http://localhost:3015
|
VITE_APP_AI_BACKEND=
|
||||||
|
|
||||||
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
|
# VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
|
||||||
|
|
||||||
# put these in your .env.local, or make sure you don't commit!
|
# put these in your .env.local, or make sure you don't commit!
|
||||||
# must be lowercase `true` when turned on
|
# must be lowercase `true` when turned on
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
|
VITE_APP_BACKEND_V2_GET_URL=http://localhost:8080/api/v2/scenes/
|
||||||
VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
|
VITE_APP_BACKEND_V2_POST_URL=http://localhost:8080/api/v2/scenes/
|
||||||
|
|
||||||
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
|
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
|
||||||
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
|
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
|
||||||
|
|
||||||
VITE_APP_PORTAL_URL=https://portal.excalidraw.com
|
VITE_APP_STORAGE_BACKEND=http
|
||||||
|
VITE_APP_HTTP_STORAGE_BACKEND_URL=http://localhost:5011/api/v2
|
||||||
|
|
||||||
|
VITE_APP_PORTAL_URL=
|
||||||
|
|
||||||
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
||||||
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
||||||
|
|
||||||
VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
|
VITE_APP_AI_BACKEND=
|
||||||
|
|
||||||
# Fill to set socket server URL used for collaboration.
|
# Fill to set socket server URL used for collaboration.
|
||||||
# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
|
# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
|
||||||
VITE_APP_WS_SERVER_URL=
|
VITE_APP_WS_SERVER_URL=http://localhost:5012
|
||||||
|
|
||||||
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
|
# VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
|
||||||
|
|
||||||
VITE_APP_DISABLE_TRACKING=
|
VITE_APP_DISABLE_TRACKING=true
|
||||||
|
VITE_APP_DISABLE_SENTRY=true
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.local
|
.env.local
|
||||||
|
.env
|
||||||
.env.production.local
|
.env.production.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.envrc
|
.envrc
|
||||||
|
17
Dockerfile
17
Dockerfile
@ -2,16 +2,23 @@ FROM node:18 AS build
|
|||||||
|
|
||||||
WORKDIR /opt/node_app
|
WORKDIR /opt/node_app
|
||||||
|
|
||||||
COPY package.json yarn.lock ./
|
FROM build as production_buildstage
|
||||||
RUN yarn --ignore-optional --network-timeout 600000
|
|
||||||
|
|
||||||
ARG NODE_ENV=production
|
COPY package.json yarn.lock ./
|
||||||
|
COPY excalidraw-app/package.json ./excalidraw-app/
|
||||||
|
COPY packages/excalidraw/package.json ./packages/excalidraw/
|
||||||
|
|
||||||
|
RUN yarn --network-timeout 600000
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
ARG NODE_ENV=production
|
||||||
RUN yarn build:app:docker
|
RUN yarn build:app:docker
|
||||||
|
|
||||||
FROM nginx:1.21-alpine
|
FROM nginx:1.21-alpine as production
|
||||||
|
|
||||||
COPY --from=build /opt/node_app/build /usr/share/nginx/html
|
COPY --from=production_buildstage /opt/node_app/excalidraw-app/build /usr/share/nginx/html
|
||||||
|
|
||||||
HEALTHCHECK CMD wget -q -O /dev/null http://localhost || exit 1
|
HEALTHCHECK CMD wget -q -O /dev/null http://localhost || exit 1
|
||||||
|
|
||||||
|
FROM build as development
|
81
README.md
81
README.md
@ -1,23 +1,55 @@
|
|||||||
<a href="https://excalidraw.com/" target="_blank" rel="noopener">
|
# Excalidraw without firebase
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" alt="Excalidraw" srcset="https://excalidraw.nyc3.cdn.digitaloceanspaces.com/github/excalidraw_github_cover_2_dark.png" />
|
This is a fork from the [excalidraw project](https://github.com/excalidraw/excalidraw) with changes inspired and partly taken from [Kilian Decaderincourt](https://gitlab.com/kiliandeca/excalidraw-fork) to enable support for rooms without using firebase.<br />
|
||||||
<img alt="Excalidraw" src="https://excalidraw.nyc3.cdn.digitaloceanspaces.com/github/excalidraw_github_cover_2.png" />
|
|
||||||
</picture>
|
## Setup with docker
|
||||||
|
|
||||||
|
Please copy the .env.development.default or .env.production.default file to .env (or with environment without default at the end) and change it according to your needs, see [react-scripts](https://create-react-app.dev/docs/adding-custom-environment-variables/).
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
docker-compose exec excalidraw yarn install
|
||||||
|
docker-compose exec excalidraw yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
Hint: Collab mode requires a secure context (https). Localhost works as well, but not http over local network.
|
||||||
|
|
||||||
|
#### Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ------------------ | --------------------------------- |
|
||||||
|
| `yarn` | Install the dependencies |
|
||||||
|
| `yarn start` | Run the project |
|
||||||
|
| `yarn fix` | Reformat all files with Prettier |
|
||||||
|
| `yarn test` | Run tests |
|
||||||
|
| `yarn test:update` | Update test snapshots |
|
||||||
|
| `yarn test:code` | Test for formatting with Prettier |
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose -f docker-compose-prod.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Additional licence
|
||||||
|
|
||||||
|
The excalidraw [logo](https://thenounproject.com/icon/2357486/) in this repo – created by [Verry](https://thenounproject.com/verry.dsign.creative) – is licenced under [CC BY 3.0 Unported](https://creativecommons.org/licenses/by/3.0/).
|
||||||
|
<div align="center" style="display:flex;flex-direction:column;"}>
|
||||||
|
<a href="https://excalidraw.com">
|
||||||
|
<img width="540" src="./public/og-image-sm.png" alt="Excalidraw logo: Sketch handrawn like diagrams."/>
|
||||||
</a>
|
</a>
|
||||||
|
<h3>Virtual whiteboard for sketching hand-drawn like diagrams.<br/>Collaborative and end-to-end encrypted.</h3>
|
||||||
<h4 align="center">
|
<p>
|
||||||
<a href="https://excalidraw.com">Excalidraw Editor</a> |
|
<a href="https://twitter.com/excalidraw">
|
||||||
<a href="https://blog.excalidraw.com">Blog</a> |
|
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/>
|
||||||
<a href="https://docs.excalidraw.com">Documentation</a> |
|
</a>
|
||||||
<a href="https://plus.excalidraw.com">Excalidraw+</a>
|
<a href="https://discord.gg/UexuTaE">
|
||||||
</h4>
|
<img alt="Chat with us on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/>
|
||||||
|
</a>
|
||||||
<div align="center">
|
</p>
|
||||||
<h2>
|
|
||||||
An open source virtual hand-drawn style whiteboard. </br>
|
|
||||||
Collaborative and end-to-end encrypted. </br>
|
|
||||||
<br />
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
@ -127,3 +159,14 @@ If you like the project, you can become a sponsor at [Open Collective](https://o
|
|||||||
Last but not least, we're thankful to these companies for offering their services for free:
|
Last but not least, we're thankful to these companies for offering their services for free:
|
||||||
|
|
||||||
[![Vercel](./.github/assets/vercel.svg)](https://vercel.com) [![Sentry](./.github/assets/sentry.svg)](https://sentry.io) [![Crowdin](./.github/assets/crowdin.svg)](https://crowdin.com)
|
[![Vercel](./.github/assets/vercel.svg)](https://vercel.com) [![Sentry](./.github/assets/sentry.svg)](https://sentry.io) [![Crowdin](./.github/assets/crowdin.svg)](https://crowdin.com)
|
||||||
|
|
||||||
|
## Developers
|
||||||
|
|
||||||
|
You can integrate Excalidraw into your app by installing our [npm component](https://npmjs.com/package/@excalidraw/excalidraw).
|
||||||
|
|
||||||
|
Visit our documentation on [https://docs.excalidraw.com](https://docs.excalidraw.com).
|
||||||
|
|
||||||
|
## Who's integrating Excalidraw
|
||||||
|
|
||||||
|
[Google Cloud](https://googlecloudcheatsheet.withgoogle.com/architecture) • [Meta](https://meta.com/) • [CodeSandbox](https://codesandbox.io/) • [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) • [Replit](https://replit.com/) • [Slite](https://slite.com/) • [Notion](https://notion.so/) • [HackerRank](https://www.hackerrank.com/)
|
||||||
|
```
|
44
docker-compose-prod.yml
Normal file
44
docker-compose-prod.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
excalidraw:
|
||||||
|
stdin_open: true
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: production
|
||||||
|
container_name: excalidraw
|
||||||
|
ports:
|
||||||
|
- "5010:80"
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
disable: true
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
|
||||||
|
excalidraw-storage-backend:
|
||||||
|
build:
|
||||||
|
context: https://github.com/kitsteam/excalidraw-storage-backend.git#main
|
||||||
|
target: production
|
||||||
|
ports:
|
||||||
|
- "5011:8080"
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
STORAGE_URI: redis://:${REDIS_PASSWORD}@redis:6379
|
||||||
|
STORAGE_TTL: 2592000000
|
||||||
|
|
||||||
|
excalidraw-room:
|
||||||
|
image: excalidraw/excalidraw-room
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "5012:80"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
notused:
|
||||||
|
redis_data:
|
@ -2,24 +2,43 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
excalidraw:
|
excalidraw:
|
||||||
|
stdin_open: true
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
target: development
|
||||||
- NODE_ENV=development
|
|
||||||
container_name: excalidraw
|
container_name: excalidraw
|
||||||
ports:
|
ports:
|
||||||
- "3000:80"
|
- "3000:3000"
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
stdin_open: true
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
disable: true
|
disable: true
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/opt/node_app/app:delegated
|
- node_modules:/opt/node_app/node_modules
|
||||||
- ./package.json:/opt/node_app/package.json
|
- ./:/opt/node_app/
|
||||||
- ./yarn.lock:/opt/node_app/yarn.lock
|
|
||||||
- notused:/opt/node_app/app/node_modules
|
|
||||||
|
|
||||||
|
excalidraw-storage-backend:
|
||||||
|
stdin_open: true
|
||||||
|
build:
|
||||||
|
context: https://github.com/kitsteam/excalidraw-storage-backend.git#main
|
||||||
|
target: production
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
STORAGE_URI: redis://:${REDIS_PASSWORD}@redis:6379
|
||||||
|
STORAGE_TTL: 2592000000
|
||||||
|
|
||||||
|
excalidraw-room:
|
||||||
|
image: excalidraw/excalidraw-room
|
||||||
|
ports:
|
||||||
|
- "5001:80"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
notused:
|
- redis_data:/data
|
||||||
|
volumes:
|
||||||
|
redis_data:
|
||||||
|
node_modules:
|
||||||
|
@ -97,6 +97,7 @@ import { AppFooter } from "./components/AppFooter";
|
|||||||
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
||||||
import { useAtomWithInitialValue } from "../packages/excalidraw/jotai";
|
import { useAtomWithInitialValue } from "../packages/excalidraw/jotai";
|
||||||
import { appJotaiStore } from "./app-jotai";
|
import { appJotaiStore } from "./app-jotai";
|
||||||
|
import { getStorageBackend } from "./data/config";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { ResolutionType } from "../packages/excalidraw/utility-types";
|
import { ResolutionType } from "../packages/excalidraw/utility-types";
|
||||||
@ -354,11 +355,15 @@ const ExcalidrawWrapper = () => {
|
|||||||
}, [] as FileId[]) || [];
|
}, [] as FileId[]) || [];
|
||||||
|
|
||||||
if (data.isExternalScene) {
|
if (data.isExternalScene) {
|
||||||
loadFilesFromFirebase(
|
getStorageBackend()
|
||||||
|
.then((storageBackend) => {
|
||||||
|
return storageBackend.loadFilesFromStorageBackend(
|
||||||
`${FIREBASE_STORAGE_PREFIXES.shareLinkFiles}/${data.id}`,
|
`${FIREBASE_STORAGE_PREFIXES.shareLinkFiles}/${data.id}`,
|
||||||
data.key,
|
data.key,
|
||||||
fileIds,
|
fileIds,
|
||||||
).then(({ loadedFiles, erroredFiles }) => {
|
);
|
||||||
|
})
|
||||||
|
.then(({ loadedFiles, erroredFiles }) => {
|
||||||
excalidrawAPI.addFiles(loadedFiles);
|
excalidrawAPI.addFiles(loadedFiles);
|
||||||
updateStaleImageStatuses({
|
updateStaleImageStatuses({
|
||||||
excalidrawAPI,
|
excalidrawAPI,
|
||||||
|
@ -83,6 +83,7 @@ import { atom, useAtom } from "jotai";
|
|||||||
import { appJotaiStore } from "../app-jotai";
|
import { appJotaiStore } from "../app-jotai";
|
||||||
import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
|
import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
|
||||||
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
|
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||||
|
import { getStorageBackend } from "../data/config";
|
||||||
|
|
||||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||||
export const collabDialogShownAtom = atom(false);
|
export const collabDialogShownAtom = atom(false);
|
||||||
@ -140,7 +141,12 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
throw new AbortError();
|
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 }) => {
|
saveFiles: async ({ addedFiles }) => {
|
||||||
const { roomId, roomKey } = this.portal;
|
const { roomId, roomKey } = this.portal;
|
||||||
@ -148,7 +154,8 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
throw new AbortError();
|
throw new AbortError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveFilesToFirebase({
|
const storageBackend = await getStorageBackend();
|
||||||
|
return storageBackend.saveFilesToStorageBackend({
|
||||||
prefix: `${FIREBASE_STORAGE_PREFIXES.collabFiles}/${roomId}`,
|
prefix: `${FIREBASE_STORAGE_PREFIXES.collabFiles}/${roomId}`,
|
||||||
files: await encodeFilesForUpload({
|
files: await encodeFilesForUpload({
|
||||||
files: addedFiles,
|
files: addedFiles,
|
||||||
@ -267,11 +274,8 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
syncableElements: readonly SyncableExcalidrawElement[],
|
syncableElements: readonly SyncableExcalidrawElement[],
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const savedData = await saveToFirebase(
|
const storageBackend = await getStorageBackend();
|
||||||
this.portal,
|
const savedData = await storageBackend.saveToStorageBackend(this.portal, syncableElements, this.excalidrawAPI.getAppState());
|
||||||
syncableElements,
|
|
||||||
this.excalidrawAPI.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.isCollaborating() && savedData && savedData.reconciledElements) {
|
if (this.isCollaborating() && savedData && savedData.reconciledElements) {
|
||||||
this.handleRemoteSceneUpdate(
|
this.handleRemoteSceneUpdate(
|
||||||
@ -656,7 +660,8 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
this.excalidrawAPI.resetScene();
|
this.excalidrawAPI.resetScene();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const elements = await loadFromFirebase(
|
const storageBackend = await getStorageBackend();
|
||||||
|
const elements = await storageBackend.loadFromStorageBackend(
|
||||||
roomLinkData.roomId,
|
roomLinkData.roomId,
|
||||||
roomLinkData.roomKey,
|
roomLinkData.roomKey,
|
||||||
this.portal.socket,
|
this.portal.socket,
|
||||||
|
@ -23,7 +23,7 @@ import type { Socket } from "socket.io-client";
|
|||||||
|
|
||||||
class Portal {
|
class Portal {
|
||||||
collab: TCollabClass;
|
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
|
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
|
||||||
roomId: string | null = null;
|
roomId: string | null = null;
|
||||||
roomKey: string | null = null;
|
roomKey: string | null = null;
|
||||||
@ -33,7 +33,7 @@ class Portal {
|
|||||||
this.collab = collab;
|
this.collab = collab;
|
||||||
}
|
}
|
||||||
|
|
||||||
open(socket: Socket, id: string, key: string) {
|
open(socket: SocketIOClient.Socket, id: string, key: string) {
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.roomId = id;
|
this.roomId = id;
|
||||||
this.roomKey = key;
|
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 { reconcileElements } from "../collab/reconciliation";
|
||||||
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
||||||
import { ResolutionType } from "../../packages/excalidraw/utility-types";
|
import { ResolutionType } from "../../packages/excalidraw/utility-types";
|
||||||
import type { Socket } from "socket.io-client";
|
import { Socket } from "socket.io-client";
|
||||||
|
|
||||||
// private
|
// private
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -49,8 +49,10 @@ const _loadFirebase = async () => {
|
|||||||
const firebase = (
|
const firebase = (
|
||||||
await import(/* webpackChunkName: "firebase" */ "firebase/app")
|
await import(/* webpackChunkName: "firebase" */ "firebase/app")
|
||||||
).default;
|
).default;
|
||||||
|
const storage = import.meta.env.VITE_APP_STORAGE_BACKEND;
|
||||||
|
const useFirebase = storage === "firebase";
|
||||||
|
|
||||||
if (!isFirebaseInitialized) {
|
if (useFirebase && !isFirebaseInitialized) {
|
||||||
try {
|
try {
|
||||||
firebase.initializeApp(FIREBASE_CONFIG);
|
firebase.initializeApp(FIREBASE_CONFIG);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -139,12 +141,12 @@ const decryptElements = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
class FirebaseSceneVersionCache {
|
class FirebaseSceneVersionCache {
|
||||||
private static cache = new WeakMap<Socket, number>();
|
private static cache = new WeakMap<SocketIOClient.Socket, number>();
|
||||||
static get = (socket: Socket) => {
|
static get = (socket: SocketIOClient.Socket) => {
|
||||||
return FirebaseSceneVersionCache.cache.get(socket);
|
return FirebaseSceneVersionCache.cache.get(socket);
|
||||||
};
|
};
|
||||||
static set = (
|
static set = (
|
||||||
socket: Socket,
|
socket: SocketIOClient.Socket,
|
||||||
elements: readonly SyncableExcalidrawElement[],
|
elements: readonly SyncableExcalidrawElement[],
|
||||||
) => {
|
) => {
|
||||||
FirebaseSceneVersionCache.cache.set(socket, getSceneVersion(elements));
|
FirebaseSceneVersionCache.cache.set(socket, getSceneVersion(elements));
|
||||||
@ -286,7 +288,7 @@ export const saveToFirebase = async (
|
|||||||
export const loadFromFirebase = async (
|
export const loadFromFirebase = async (
|
||||||
roomId: string,
|
roomId: string,
|
||||||
roomKey: string,
|
roomKey: string,
|
||||||
socket: Socket | null,
|
socket: SocketIOClient.Socket | null,
|
||||||
): Promise<readonly ExcalidrawElement[] | null> => {
|
): Promise<readonly ExcalidrawElement[] | null> => {
|
||||||
const firebase = await loadFirestore();
|
const firebase = await loadFirestore();
|
||||||
const db = firebase.firestore();
|
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";
|
} from "../../packages/excalidraw/data/encode";
|
||||||
import {
|
import {
|
||||||
decryptData,
|
decryptData,
|
||||||
|
encryptData,
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
IV_LENGTH_BYTES,
|
IV_LENGTH_BYTES,
|
||||||
} from "../../packages/excalidraw/data/encryption";
|
} from "../../packages/excalidraw/data/encryption";
|
||||||
@ -33,7 +34,7 @@ import {
|
|||||||
WS_SUBTYPES,
|
WS_SUBTYPES,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import { encodeFilesForUpload } from "./FileManager";
|
import { encodeFilesForUpload } from "./FileManager";
|
||||||
import { saveFilesToFirebase } from "./firebase";
|
import { getStorageBackend } from "./config";
|
||||||
|
|
||||||
export type SyncableExcalidrawElement = ExcalidrawElement & {
|
export type SyncableExcalidrawElement = ExcalidrawElement & {
|
||||||
_brand: "SyncableExcalidrawElement";
|
_brand: "SyncableExcalidrawElement";
|
||||||
@ -343,7 +344,8 @@ export const exportToBackend = async (
|
|||||||
url.hash = `json=${json.id},${encryptionKey}`;
|
url.hash = `json=${json.id},${encryptionKey}`;
|
||||||
const urlString = url.toString();
|
const urlString = url.toString();
|
||||||
|
|
||||||
await saveFilesToFirebase({
|
const storageBackend = await getStorageBackend();
|
||||||
|
await storageBackend.saveFilesToStorageBackend({
|
||||||
prefix: `/files/shareLinks/${json.id}`,
|
prefix: `/files/shareLinks/${json.id}`,
|
||||||
files: filesToUpload,
|
files: filesToUpload,
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"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:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build",
|
||||||
"build:version": "node ../scripts/build-version.js",
|
"build:version": "node ../scripts/build-version.js",
|
||||||
"build": "yarn build:app && yarn build:version",
|
"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_GIT_SHA: string;
|
||||||
|
|
||||||
|
VITE_APP_HTTP_STORAGE_BACKEND_URL: string;
|
||||||
|
VITE_APP_STORAGE_BACKEND: "http" | "firebase";
|
||||||
|
|
||||||
MODE: string;
|
MODE: string;
|
||||||
|
|
||||||
DEV: string;
|
DEV: string;
|
||||||
|
@ -10,9 +10,8 @@ const envVars = loadEnv("", `../`);
|
|||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
port: Number(envVars.VITE_APP_PORT || 3000),
|
host: '0.0.0.0',
|
||||||
// open the browser
|
port: Number(envVars.VITE_APP_PORT || 3000)
|
||||||
open: true,
|
|
||||||
},
|
},
|
||||||
// We need to specify the envDir since now there are no
|
// 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
|
//more located in parallel with the vite.config.ts file but in parent dir
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"socket.io-client": "4.7.2"
|
"socket.io-client": "4.7.2"
|
||||||
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@excalidraw/eslint-config": "1.0.3",
|
"@excalidraw/eslint-config": "1.0.3",
|
||||||
@ -58,9 +59,9 @@
|
|||||||
"prettier": "@excalidraw/prettier-config",
|
"prettier": "@excalidraw/prettier-config",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"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": "yarn --cwd ./excalidraw-app build:app:docker",
|
||||||
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build",
|
"build:app": "yarn --cwd ./excalidraw-app build:app",
|
||||||
"build:version": "node ./scripts/build-version.js",
|
"build:version": "yarn --cwd ./excalidraw-app build:version",
|
||||||
"build": "yarn --cwd ./excalidraw-app build",
|
"build": "yarn --cwd ./excalidraw-app build",
|
||||||
"fix:code": "yarn test:code --fix",
|
"fix:code": "yarn test:code --fix",
|
||||||
"fix:other": "yarn prettier --write",
|
"fix:other": "yarn prettier --write",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user