Compare commits

..

3 Commits

Author SHA1 Message Date
Panayiotis Lipiridis
6870f55f7d slash 2020-03-12 00:26:28 +02:00
Panayiotis Lipiridis
3bf9c13eba Merge 2020-03-12 00:25:37 +02:00
Panayiotis Lipiridis
313425537a Update readme and start script 2020-03-12 00:24:58 +02:00
22 changed files with 982 additions and 1419 deletions

View File

@ -1,2 +0,0 @@
PORT=
CORS_ORIGIN=

View File

@ -1,3 +0,0 @@
{
"extends": "@excalidraw/eslint-config"
}

5
.gcloudignore Normal file
View File

@ -0,0 +1,5 @@
# Exclude compiled .js files
*.js
# Exclude dependencies
node_modules/

View File

@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: sunday
time: "01:00"

View File

@ -1,18 +1,20 @@
name: Lint name: Lint
on: on: pull_request
push:
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Setup Node.js 14.x
- name: Setup Node.js 12.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 12.x
- name: Install dependencies
run: yarn --frozen-lockfile - name: Install and lint
- name: Lint run: |
run: yarn test:other yarn
yarn lint

View File

@ -1,19 +0,0 @@
name: Publish Docker
on:
push:
branches:
- master
jobs:
publish-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/build-push-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: excalidraw/excalidraw-room
tag_with_ref: true
tag_with_sha: true

View File

@ -1,20 +0,0 @@
name: Test & Build
on:
push:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Test and Build
run: |
yarn test:code
yarn build

111
.gitignore vendored
View File

@ -1,108 +1,3 @@
# Logs *.js
logs node_modules
*.log yarn-error.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
.DS_Store
dist

1
.prettierrc Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,13 +0,0 @@
FROM node:12-alpine
WORKDIR /excalidraw-room
COPY package.json yarn.lock ./
RUN yarn
COPY tsconfig.json ./
COPY src ./src
RUN yarn build
EXPOSE 80
CMD ["yarn", "start"]

43
README.md Executable file → Normal file
View File

@ -1,27 +1,30 @@
# Example of excalidraw collaboration server # excalidraw-room
Collaboration server for Excalidraw The backend for collaboration in [Excalidraw](https://excalidraw.com).
If you need to use cluster mode with pm2. Checkout: https://socket.io/docs/v4/pm2/ ## Requirements
If you are not familiar with pm2: https://pm2.keymetrics.io/docs/usage/quick-start/ - [Node.js](https://nodejs.org)
- [Google Cloud SDK](https://cloud.google.com/sdk/)
# Development ## Install dependencies
- install
```sh
yarn
```
- run development server
```sh
yarn start:dev
```
# Start with pm2
``` ```
pm2 start pm2.production.json yarn
```
## Run locally
```
yarn start
```
Visit [`localhost:8080`](http://localhost:8080) to test it.
## Deploy to GCP
Make sure you have access to the [`excalidraw-room`](https://console.cloud.google.com/home/dashboard?project=excalidraw-room) project.
```
yarn deploy
``` ```

2
app.yaml Normal file
View File

@ -0,0 +1,2 @@
runtime: nodejs
env: flex

70
index.ts Normal file
View File

@ -0,0 +1,70 @@
// source: https://github.com/idlewinn/collab-server/blob/master/src/index.ts
import express from "express";
import http from "http";
import socketIO from "socket.io";
const app = express();
const port = process.env.PORT || 8080;
app.get("/", (req, res) => {
res.send("Hi, excalidraw-room!");
});
const server = http.createServer(app);
server.listen(port, () => {
console.log(`listening on port: ${port}`);
});
const io = socketIO(server, {
handlePreflightRequest: function(req, res) {
var headers = {
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Allow-Origin": req.header ? req.header.origin : "*",
"Access-Control-Allow-Credentials": true
};
res.writeHead(200, headers);
res.end();
}
});
io.on("connection", socket => {
console.log("connection established!");
io.to(`${socket.id}`).emit("init-room");
socket.on("join-room", roomID => {
console.log(`${socket.id} has joined ${roomID}`);
socket.join(roomID);
if (io.sockets.adapter.rooms[roomID].length <= 1) {
io.to(`${socket.id}`).emit("first-in-room");
} else {
socket.broadcast.to(roomID).emit("new-user", socket.id);
}
io.in(roomID).emit(
"room-user-count",
io.sockets.adapter.rooms[roomID].length
);
});
socket.on(
"server-broadcast",
(roomID: string, encryptedData: ArrayBuffer, iv: Uint8Array) => {
console.log(`${socket.id} sends update to ${roomID}`);
socket.broadcast.to(roomID).emit("client-broadcast", encryptedData, iv);
}
);
socket.on("disconnecting", () => {
const rooms = io.sockets.adapter.rooms;
for (const roomID in socket.rooms) {
const remaining = rooms[roomID].length - 1;
if (remaining > 0) {
socket.broadcast.to(roomID).emit("room-user-count", remaining);
}
}
});
socket.on("disconnect", () => {
socket.removeAllListeners();
});
});

View File

@ -1,39 +1,41 @@
{ {
"dependencies": { "dependencies": {
"@excalidraw/eslint-config": "1.0.1", "@types/express": "4.17.3",
"@excalidraw/prettier-config": "1.0.2", "@types/node": "13.9.0",
"@types/debug": "4.1.5", "@types/socket.io": "2.1.4",
"@types/express": "4.17.11",
"@types/node": "14.14.31",
"@typescript-eslint/eslint-plugin": "4.16.1",
"@typescript-eslint/parser": "4.16.1",
"cross-env": "^7.0.3",
"debug": "4.3.1",
"dotenv": "^10.0.0",
"eslint": "7.21.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-prettier": "3.3.1",
"express": "4.17.1", "express": "4.17.1",
"prettier": "2.2.1", "socket.io": "2.3.0",
"socket.io": "^4.6.1", "typescript": "3.8.3"
"ts-node-dev": "^1.1.8", },
"typescript": "4.2.3" "devDependencies": {
"husky": "4.2.3",
"lint-staged": "10.0.8",
"prettier": "1.19.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}, },
"license": "MIT", "license": "MIT",
"main": "dist/index.js", "lint-staged": {
"name": "excalidraw-portal", "*.{ts,md,json,yaml,yml}": [
"prettier": "@excalidraw/prettier-config", "prettier --write"
]
},
"main": "index.js",
"name": "excalidraw-room",
"scripts": { "scripts": {
"build": "tsc", "build": "yarn gcp-build",
"fix:code": "yarn test:code --fix", "deploy": "gcloud app deploy --project=excalidraw-room",
"fix:other": "yarn prettier --write", "fix": "yarn prettier --write",
"fix": "yarn fix:other && yarn fix:code", "gcp-build": "tsc -p .",
"prettier": "prettier . --ignore-path=.gitignore", "lint": "yarn prettier --list-different",
"start": "node dist/index.js", "prepare": "yarn gcp-build",
"start:dev": "cross-env NODE_ENV=development ts-node-dev --respawn --transpile-only src/index.ts", "pretest": "yarn gcp-build",
"test:code": "eslint --ext .ts .", "prettier": "prettier \"**/*.{ts,md,json,yaml,yml}\"",
"test:other": "yarn prettier --list-different", "start": "yarn build && node ./index.js",
"test": "yarn test:other && yarn test:code" "test": "yarn lint"
}, },
"version": "1.0.0" "version": "1.0.0"
} }

View File

@ -1,14 +0,0 @@
{
"name": "excalidraw-collab-dev",
"script": "./dist/index.js",
"watch": ["src/"],
"ignore_watch": ["node_modules", "public"],
"autorestart": false,
"exec_mode": "fork_mode",
"instances": 1,
"env": {
"NODE_ENV": "development",
"TZ": "Europe/London"
},
"node_args": ["--inspect=127.0.0.1:9320"]
}

View File

@ -1,15 +0,0 @@
{
"name": "excalidraw-collab",
"script": "./dist/index.js",
"ignore_watch": ["node_modules", "public"],
"max_memory_restart": "4G",
"watch": false,
"wait_ready": true,
"log_date_format": "YYYY-MM-DD HH:mm Z",
"autorestart": true,
"exec_mode": "fork_mode",
"instances": 1,
"env": {
"NODE_ENV": "production"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -1,104 +0,0 @@
import debug from "debug";
import express from "express";
import http from "http";
import { Server as SocketIO } from "socket.io";
const serverDebug = debug("server");
const ioDebug = debug("io");
const socketDebug = debug("socket");
require("dotenv").config(
process.env.NODE_ENV !== "development"
? { path: ".env.production" }
: { path: ".env.development" },
);
const app = express();
const port =
process.env.PORT || (process.env.NODE_ENV !== "development" ? 80 : 3002); // default port to listen
app.use(express.static("public"));
app.get("/", (req, res) => {
res.send("Excalidraw collaboration server is up :)");
});
const server = http.createServer(app);
server.listen(port, () => {
serverDebug(`listening on port: ${port}`);
});
try {
const io = new SocketIO(server, {
transports: ["websocket", "polling"],
cors: {
allowedHeaders: ["Content-Type", "Authorization"],
origin: process.env.CORS_ORIGIN || "*",
credentials: true,
},
allowEIO3: true,
});
io.on("connection", (socket) => {
ioDebug("connection established!");
io.to(`${socket.id}`).emit("init-room");
socket.on("join-room", async (roomID) => {
socketDebug(`${socket.id} has joined ${roomID}`);
await socket.join(roomID);
const sockets = await io.in(roomID).fetchSockets();
if (sockets.length <= 1) {
io.to(`${socket.id}`).emit("first-in-room");
} else {
socketDebug(`${socket.id} new-user emitted to room ${roomID}`);
socket.broadcast.to(roomID).emit("new-user", socket.id);
}
io.in(roomID).emit(
"room-user-change",
sockets.map((socket) => socket.id),
);
});
socket.on(
"server-broadcast",
(roomID: string, encryptedData: ArrayBuffer, iv: Uint8Array) => {
socketDebug(`${socket.id} sends update to ${roomID}`);
socket.broadcast.to(roomID).emit("client-broadcast", encryptedData, iv);
},
);
socket.on(
"server-volatile-broadcast",
(roomID: string, encryptedData: ArrayBuffer, iv: Uint8Array) => {
socketDebug(`${socket.id} sends volatile update to ${roomID}`);
socket.volatile.broadcast
.to(roomID)
.emit("client-broadcast", encryptedData, iv);
},
);
socket.on("disconnecting", async () => {
socketDebug(`${socket.id} has disconnected`);
for (const roomID in socket.rooms) {
const otherClients = (await io.in(roomID).fetchSockets()).filter(
(_socket) => _socket.id !== socket.id,
);
if (otherClients.length > 0) {
socket.broadcast.to(roomID).emit(
"room-user-change",
otherClients.map((socket) => socket.id),
);
}
}
});
socket.on("disconnect", () => {
socket.removeAllListeners();
socket.disconnect();
});
});
} catch (error) {
console.error(error);
}

14
tsconfig.json Executable file → Normal file
View File

@ -1,16 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "module": "CommonJS"
"strict": true, },
"forceConsistentCasingInFileNames": true, "include": ["*.ts"]
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"outDir": "dist"
}
} }

1877
yarn.lock

File diff suppressed because it is too large Load Diff