diff --git a/README.md b/README.md new file mode 100644 index 0000000..50e56c4 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# HTML Canvas with WebSocket + +## About + +This is a demo app that allows you to draw on an HTML canvas. Everythng that gets drawn will be +transmitted to a server using websocket and displayed by a receiver app. + +## Dependencies + +This app needs Node.js and npm to build and run. + +## Running the app + +Open the command line in the project directory and run ```npm run launch``` + +Then open your browser and navigate to http://localhost:8080. diff --git a/package.json b/package.json index 887d544..01194f0 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "watch": "tsc -watch -p ./", "serve": "serve -l 8080 .", "debug": "npm-run-all --parallel watch serve", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "run-server": "cd server && npm run launch", + "launch": "npm-run-all --parallel run-server build serve" }, "author": "", "license": "ISC", diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..8781662 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,102 @@ +dist/ + +node_modules/ +.node_modules/ +built/* +tests/cases/rwc/* +tests/cases/test262/* +tests/cases/perf/* +!tests/cases/webharness/compilerToString.js +test-args.txt +~*.docx +\#*\# +.\#* +tests/baselines/local/* +tests/baselines/local.old/* +tests/services/baselines/local/* +tests/baselines/prototyping/local/* +tests/baselines/rwc/* +tests/baselines/test262/* +tests/baselines/reference/projectOutput/* +tests/baselines/local/projectOutput/* +tests/baselines/reference/testresults.tap +tests/services/baselines/prototyping/local/* +tests/services/browser/typescriptServices.js +src/harness/*.js +src/compiler/diagnosticInformationMap.generated.ts +src/compiler/diagnosticMessages.generated.json +src/parser/diagnosticInformationMap.generated.ts +src/parser/diagnosticMessages.generated.json +rwc-report.html +*.swp +build.json +*.actual +tests/webTestServer.js +tests/webTestServer.js.map +tests/webhost/*.d.ts +tests/webhost/webtsc.js +tests/cases/**/*.js +!tests/cases/docker/*.js/ +tests/cases/**/*.js.map +*.config +scripts/eslint/built/ +scripts/debug.bat +scripts/run.bat +scripts/word2md.js +scripts/buildProtocol.js +scripts/ior.js +scripts/authors.js +scripts/configurePrerelease.js +scripts/configureLanguageServiceBuild.js +scripts/open-user-pr.js +scripts/open-cherry-pick-pr.js +scripts/processDiagnosticMessages.d.ts +scripts/processDiagnosticMessages.js +scripts/produceLKG.js +scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js +scripts/generateLocalizedDiagnosticMessages.js +scripts/request-pr-review.js +scripts/*.js.map +scripts/typings/ +coverage/ +internal/ +**/.DS_Store +.settings +**/.vs +**/.vscode/* +!**/.vscode/tasks.json +!**/.vscode/settings.template.json +!**/.vscode/launch.template.json +!**/.vscode/extensions.json +!tests/cases/projects/projectOption/**/node_modules +!tests/cases/projects/NodeModulesSearch/**/* +!tests/baselines/reference/project/nodeModules*/**/* +.idea +yarn.lock +yarn-error.log +.parallelperf.* +tests/cases/user/*/package-lock.json +tests/cases/user/*/node_modules/ +tests/cases/user/*/**/*.js +tests/cases/user/*/**/*.js.map +tests/cases/user/*/**/*.d.ts +!tests/cases/user/zone.js/ +!tests/cases/user/bignumber.js/ +!tests/cases/user/discord.js/ +tests/baselines/reference/dt +.failed-tests +TEST-results.xml +package-lock.json +tests/cases/user/npm/npm +tests/cases/user/TypeScript-React-Starter/TypeScript-React-Starter +tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter +tests/cases/user/TypeScript-React-Native-Starter/TypeScript-React-Native-Starter +tests/cases/user/TypeScript-Vue-Starter/TypeScript-Vue-Starter +tests/cases/user/TypeScript-WeChat-Starter/TypeScript-WeChat-Starter +tests/cases/user/create-react-app/create-react-app +tests/cases/user/fp-ts/fp-ts +tests/cases/user/webpack/webpack +tests/cases/user/puppeteer/puppeteer +tests/cases/user/axios-src/axios-src +tests/cases/user/prettier/prettier +.eslintcache diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..9d4ee84 --- /dev/null +++ b/server/package.json @@ -0,0 +1,17 @@ +{ + "scripts": { + "build": "npm install && tsc -p ./", + "start": "node dist/app.js", + "launch": "npm-run-all build start" + }, + "devDependencies": { + "@tsconfig/node16": "^1.0.2", + "@types/node": "^16.7.1", + "@types/ws": "^7.4.7", + "npm-run-all": "^4.1.5", + "typescript": "^4.3.5" + }, + "dependencies": { + "ws": "^8.2.0" + } +} diff --git a/server/ts/app.ts b/server/ts/app.ts new file mode 100644 index 0000000..ab87a61 --- /dev/null +++ b/server/ts/app.ts @@ -0,0 +1,102 @@ +import WebSocket from 'ws'; +import Coordinates from './coordinates'; + +const wss = new WebSocket.Server({ port: 8081 }); +const receivers: WebSocket[] = []; +let sender: WebSocket | null = null; + +const paths = [] as Coordinates[][]; + +let currentPath = [] as Coordinates[]; + +wss.on('connection', ws => { + console.log('CONNECTION incoming'); + ws.once('message', message => { + const str = message.toString(); + switch (str) { + case 'SENDER': + setSender(ws); + break; + case 'RECEIVER': + addReceiver(ws) + break; + default: + console.log('Unknown register code: ' + str); + break; + } + }); +}); + +console.log('READY'); + +function addReceiver(ws: WebSocket) { + console.log('RECEIVER registered'); + for (const path of paths) { + ws.send('START'); + path.forEach(c => ws.send(JSON.stringify(c))); + ws.send('STOP'); + } + + receivers.push(ws); + + ws.onclose = () => { + // Remove receiver when connection closes + for (const [i, recv] of receivers.entries()) { + if (ws === recv) { + receivers.splice(i, 1); + } + } + }; +} + +function setSender(ws: WebSocket) { + console.log('SENDER is being registered') + if (sender) { + console.log('Removing current sender...') + sender.close(); + clearPaths(); + } + + sender = ws; + ws.on('message', msg => processMessage(msg)); +} + +function processMessage(message: WebSocket.Data) { + const text = message.toString(); + switch (text) { + case 'START': + startPath(); + break; + case 'STOP': + finishPath(); + break; + default: + processCoordinates(text); + break; + } +} + +function processCoordinates(text: string) { + console.log('COORDINATES received: ' + text); + + console.log('SENDING to ' + receivers.length, 'clients'); + receivers.forEach(r => r.send(text)); + + currentPath.push(JSON.parse(text)); +} + +function startPath() { + currentPath = []; + receivers.forEach(r => r.send('START')); +} + +function finishPath() { + paths.push(currentPath); + receivers.forEach(r => r.send("STOP")); +} + +function clearPaths() { + paths.splice(0, paths.length); + receivers.forEach(r => r.send('CLEAR')); + console.log('Paths have been cleared'); +} diff --git a/server/ts/coordinates.ts b/server/ts/coordinates.ts new file mode 100644 index 0000000..f8cceec --- /dev/null +++ b/server/ts/coordinates.ts @@ -0,0 +1,9 @@ +export default class Coordinates { + public x: number; + public y: number; + + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..8f12ae5 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@tsconfig/node16/tsconfig.json", + "compilerOptions": { + "sourceMap": true, + "outDir": "dist", + "moduleResolution": "node" + }, + "include": ["ts/**/*"], + "exclude": ["node_modules", "**/*.spec.ts"] +} diff --git a/ts/canvasReceiver.ts b/ts/canvasReceiver.ts index 5b26b27..ea806ae 100644 --- a/ts/canvasReceiver.ts +++ b/ts/canvasReceiver.ts @@ -1,8 +1,8 @@ import CanvasUtil from './canvas-util/canvasUtil.js'; import Coordinates from './canvas-util/coordinates.js'; +import { getWebsocketUrl } from "./websocket/websocket.js"; class CanvasReceiverController { - private canvas!: HTMLCanvasElement; private canvasDiv: HTMLDivElement; private canvasUtil: CanvasUtil; private ws: WebSocket; @@ -23,7 +23,7 @@ class CanvasReceiverController { alert('Canvas API unavailable, drawing will not work!'); } - this.ws = new WebSocket('ws://localhost:8081', 'json'); + this.ws = new WebSocket(getWebsocketUrl(), 'json'); this.ws.onmessage = message => this.processMessage(message); this.ws.onopen = () => this.ws.send('RECEIVER'); diff --git a/ts/canvasSender.ts b/ts/canvasSender.ts index 15b25d5..9403db1 100644 --- a/ts/canvasSender.ts +++ b/ts/canvasSender.ts @@ -1,5 +1,6 @@ import CanvasUtil from "./canvas-util/canvasUtil.js"; import Coordinates from "./canvas-util/coordinates.js"; +import { getWebsocketUrl } from "./websocket/websocket.js"; class CanvasSenderController { private canvasUtil: CanvasUtil; @@ -28,7 +29,7 @@ class CanvasSenderController { canvas.addEventListener('touchmove', e => this.onMouseMove(e.touches[0])); } - this.ws = new WebSocket('ws://localhost:8081', 'json'); + this.ws = new WebSocket(getWebsocketUrl(), 'json'); this.ws.onopen = () => this.ws.send('SENDER'); } diff --git a/ts/websocket/websocket.ts b/ts/websocket/websocket.ts new file mode 100644 index 0000000..739b4ed --- /dev/null +++ b/ts/websocket/websocket.ts @@ -0,0 +1,9 @@ +export function getWebsocketUrl(): string { + let scheme = 'ws'; + + if (document.location.protocol === 'https:') { + scheme += 's'; + } + + return scheme + '://' + document.location.hostname + ':8081'; +} diff --git a/tsconfig.json b/tsconfig.json index 7ead152..71d56fc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,4 +8,4 @@ "extends": "@tsconfig/recommended/tsconfig.json", "include": ["ts/**/*"], "exclude": ["node_modules", "**/*.spec.ts"] -} \ No newline at end of file +}