Browse Source

Websockets Example

traumschule 4 years ago
commit
82a1801df9

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/node_modules

+ 4 - 0
.prettierrc

@@ -0,0 +1,4 @@
+trailingComma: "es5"
+tabWidth: 2
+semi: false
+singleQuote: true

+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+# Websocket Example
+
+Preparation: npm i
+
+Run: npm run start
+
+Configure Ports in package.json and server/index.js

+ 53 - 0
package.json

@@ -0,0 +1,53 @@
+{
+  "name": "socket-example",
+  "version": "0.0.1",
+  "private": true,
+  "dependencies": {
+    "bootstrap": "^4.3.1",
+    "chalk": "^4.1.0",
+    "concurrently": "^4.1.1",
+    "connect-session-sequelize": "^6.0.0",
+    "cors": "^2.8.5",
+    "express": "^4.17.1",
+    "express-session": "^1.16.2",
+    "moment": "^2.24.0",
+    "morgan": "^1.9.1",
+    "multer": "^1.4.2",
+    "nodemon": "^1.19.1",
+    "passport": "^0.4.0",
+    "passport-local": "^1.0.0",
+    "pg": "^7.12.0",
+    "react": "^16.8.6",
+    "react-bootstrap": "^1.0.0-beta.9",
+    "react-dom": "^16.8.6",
+    "react-feather": "^2.0.3",
+    "react-router-dom": "^5.0.1",
+    "react-scripts": "3.1.1",
+    "react-typography": "^0.16.19",
+    "sequelize": "^5.12.2",
+    "socket.io": "^2.2.0"
+  },
+  "scripts": {
+    "start": "concurrently \"react-scripts start\" \"nodemon server\"",
+    "build": "react-scripts build",
+    "prod": "node server",
+    "test": "react-scripts test --env=jsdom",
+    "eject": "react-scripts eject",
+    "seed": "node server/db/seed.js"
+  },
+  "eslintConfig": {
+    "extends": "react-app"
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  }
+}

+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+
+    <title>Websocket Example</title>
+  </head>
+  <body>
+    <noscript>Please enable JavaScript to run this app.</noscript>
+    <div id="app"></div>
+  </body>
+</html>

+ 15 - 0
public/manifest.json

@@ -0,0 +1,15 @@
+{
+  "short_name": "sockets!",
+  "name": "Websocket Example",
+  "icons": [
+    {
+      "src": "favicon.png",
+      "sizes": "64x64 32x32 24x24 16x16",
+      "type": "image/x-icon"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}

+ 19 - 0
server/db/db.js

@@ -0,0 +1,19 @@
+const Sequelize = require("sequelize");
+const pkg = require("../../package.json");
+
+const databaseName =
+  pkg.name + (process.env.NODE_ENV === "test" ? "-test" : "");
+
+const createDB = () => {
+  const db = new Sequelize(
+    process.env.DATABASE_URL || `postgres://localhost:5432/${databaseName}`,
+    {
+      logging: false
+    }
+  );
+  return db;
+};
+
+const db = createDB();
+
+module.exports = db;

+ 6 - 0
server/db/index.js

@@ -0,0 +1,6 @@
+const db = require('./db')
+
+// register models
+require('./models')
+
+module.exports = db

+ 2 - 0
server/db/models/index.js

@@ -0,0 +1,2 @@
+//const Questions = require("./questions");
+//module.exports = { Questions };

+ 111 - 0
server/index.js

@@ -0,0 +1,111 @@
+const express = require("express");
+const path = require("path");
+const app = express();
+//const cors = require("cors");
+const morgan = require("morgan");
+const socketio = require("socket.io");
+const pg = require("pg");
+delete pg.native;
+const db = require("./db");
+const chalk = require("chalk");
+
+//const passport = require('passport')
+//const LocalStrategy = require('passport-local')
+//const session = require('express-session')
+//const SequelizeStore = require('connect-session-sequelize')(session.Store)
+//const sessionStore = new SequelizeStore({ db })
+//const bot = require('../ircbot')
+
+const PORT = process.env.PORT || 3500;
+//const URL = ["http://localhost:3400"];
+
+app.use(morgan("dev"));
+//app.use(cors({ credentials: true, origin: URL }))
+// passport.use(
+//   new LocalStrategy(async (username, password, done) => {
+//     const user = await db.models.user.findOne({ where: { username } })
+//     if (!user) {
+//       return done(null, false, { message: 'Incorrect username.' })
+//     }
+//     if (!user.correctPassword(password)) {
+//       return done(null, false, { message: 'Incorrect password.' })
+//     }
+//     return done(null, user)
+//   })
+// )
+
+//passport.serializeUser((user, cb) => cb(null, user.id))
+
+// passport.deserializeUser(async (id, cb) => {
+//   try {
+//     const user = await db.models.user.findByPk(id)
+//     if (!user) return cb(null, { id: 0 })
+//     const idType = user.isAdmin ? 'trainerId' : 'userId'
+//     const { sessions, reviews, notes, referrers } = await user.fetchMyData(
+//       idType
+//     )
+//     user.setDataValue('sessions', sessions)
+//     user.setDataValue('reviews', reviews)
+//     user.setDataValue('notes', notes)
+//     user.setDataValue('referrers', referrers)
+//     cb(null, user)
+//   } catch (err) {
+//     console.log('error', err)
+//     cb(err)
+//   }
+// })
+
+// app.use(
+//   session({
+//     secret: process.env.SESSION_SECRET || 'LAKaLIHWIUH9*&h3ISVAEOIUFHAW83w',
+//     store: sessionStore,
+//     resave: false,
+//     saveUninitialized: false,
+//     cookie: { maxAge: 3600000 },
+//   })
+// )
+//
+// app.use(passport.initialize())
+// app.use(passport.session())
+
+// body parsing middleware
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+app.use(require("body-parser").text());
+//app.use("/api", require("./api"));
+//app.use('/auth', require('./auth'))
+app.use(
+  "/static",
+  express.static(path.resolve(__dirname, "..", "build", "static"))
+);
+app.get("/manifest.json", (req, res) => {
+  res.sendFile(path.resolve(__dirname, "..", "build", "manifest.json"));
+});
+app.get("/favicon.png", (req, res) => {
+  res.sendFile(path.resolve(__dirname, "..", "build", "favicon.png"));
+});
+app.use("*", express.static(path.resolve(__dirname, "..", "build")));
+
+// error handling endware
+app.use((err, req, res, next) => {
+  console.error(err);
+  console.error(err.stack);
+  res.status(err.status || 500).send(err.message || "Internal server error.");
+  next();
+});
+
+const startListening = () => {
+  const server = app.listen(PORT, () => {
+    console.log(chalk.blue(`[Express] Listening on port ${PORT}`));
+  });
+
+  const io = socketio(server);
+  require("./socket")(io);
+};
+
+const startApp = async () => {
+  //await sessionStore.sync();
+  await startListening();
+};
+
+startApp();

+ 12 - 0
server/socket.js

@@ -0,0 +1,12 @@
+//const {} = require('../db/models')
+const chalk = require("chalk");
+
+module.exports = io => {
+  io.on("connection", socket => {
+    socket.emit("welcome", "Websockets are awesome!");
+    console.log(chalk.green(`[socket.io] Connection: ${socket.id}`));
+    socket.on("disconnect", async () => {
+      console.log(chalk.red(`[socket.io] Disconnect: ${socket.id}`));
+    });
+  });
+};

+ 60 - 0
src/App.js

@@ -0,0 +1,60 @@
+import React from "react";
+import { Layout, Loading } from "./components";
+import { withRouter } from "react-router-dom";
+import socket from "./socket";
+
+const initialState = { loading: true };
+
+class App extends React.Component {
+  initializeSocket() {
+    socket.on("connect", () => {
+      if (!socket.id) console.log("no websocket connection");
+      else console.log("my socketId:", socket.id);
+    });
+    socket.on("welcome", message => {
+      this.setState({ loading: false, message });
+    });
+  }
+
+  async fetchData() {
+    // initial axios requests go here
+    this.setState({ loading: false });
+  }
+
+  /** RENDER FUNCTIONS **/
+  renderLoading() {
+    return <Loading />;
+  }
+  renderError() {
+    if (this.state.showModal === "Error") return;
+    this.setShowModal("Error");
+  }
+  renderApp() {
+    const message = this.state.message || "Fresh and clean.";
+    return (
+      <div className="w-100 h-100 d-flex flex-grow-1 align-items-center justify-content-center">
+        <h1>{message}</h1>
+      </div>
+    );
+  }
+  render() {
+    if (this.state.loading) return this.renderLoading();
+    if (this.state.error) this.renderError();
+    if (this.state.component === "layout") return <Layout {...this.state} />;
+    return this.renderApp();
+  }
+
+  componentDidMount() {
+    this.initializeSocket();
+    //this.fetchData();
+  }
+  componentWillUnmount() {
+    console.log("unmounting...");
+  }
+  constructor() {
+    super();
+    this.state = initialState;
+  }
+}
+
+export default withRouter(App);

+ 0 - 0
src/components/Layout.js


+ 12 - 0
src/components/Loading.js

@@ -0,0 +1,12 @@
+import React from "react";
+import { Spinner } from "react-bootstrap";
+
+const Loading = props => {
+  return (
+    <div className="w-100 h-100 d-flex flex-grow-1 align-items-center justify-content-center">
+      <Spinner animation="grow" variant="dark" />
+    </div>
+  );
+};
+
+export default Loading;

+ 2 - 0
src/components/index.js

@@ -0,0 +1,2 @@
+export { default as Layout } from "./Layout";
+export { default as Loading } from "./Loading";

+ 17 - 0
src/index.js

@@ -0,0 +1,17 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import "bootstrap/dist/css/bootstrap.css";
+import "./main.css";
+import App from "./App";
+//import * as serviceWorker from "./serviceWorker";
+import { BrowserRouter as Router } from "react-router-dom";
+require("dotenv").config();
+
+ReactDOM.render(
+  <Router basename={process.env.PUBLIC_URLs}>
+    <App />
+  </Router>,
+  document.getElementById("app")
+);
+
+//serviceWorker.unregister();

+ 42 - 0
src/main.css

@@ -0,0 +1,42 @@
+html,
+body {
+  height: 100vh;
+}
+
+.main-component {
+  width: 100%;
+  height: 100%;
+  padding-top: 75px;
+  padding-left: 3%;
+  padding-right: 3%;
+}
+
+.container {
+}
+
+/**** App.js ****/
+#app {
+  height: 100%;
+}
+
+/** headlines **/
+
+h1 {
+  text-align: center;
+  margin-bottom: 1em;
+}
+h3 {
+  margin-top: 1em;
+}
+
+/** links **/
+
+.main-component a,
+.main-component a:visited {
+  color: #000;
+}
+a:hover,
+a:active {
+  color: #000;
+  text-decoration: none;
+}

+ 135 - 0
src/serviceWorker.js

@@ -0,0 +1,135 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+  window.location.hostname === 'localhost' ||
+    // [::1] is the IPv6 localhost address.
+    window.location.hostname === '[::1]' ||
+    // 127.0.0.1/8 is considered localhost for IPv4.
+    window.location.hostname.match(
+      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+    )
+)
+
+export function register(config) {
+  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+    // The URL constructor is available in all browsers that support SW.
+    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
+    if (publicUrl.origin !== window.location.origin) {
+      // Our service worker won't work if PUBLIC_URL is on a different origin
+      // from what our page is served on. This might happen if a CDN is used to
+      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+      return
+    }
+
+    window.addEventListener('load', () => {
+      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
+
+      if (isLocalhost) {
+        // This is running on localhost. Let's check if a service worker still exists or not.
+        checkValidServiceWorker(swUrl, config)
+
+        // Add some additional logging to localhost, pointing developers to the
+        // service worker/PWA documentation.
+        navigator.serviceWorker.ready.then(() => {
+          console.log(
+            'This web app is being served cache-first by a service ' +
+              'worker. To learn more, visit https://bit.ly/CRA-PWA'
+          )
+        })
+      } else {
+        // Is not localhost. Just register service worker
+        registerValidSW(swUrl, config)
+      }
+    })
+  }
+}
+
+function registerValidSW(swUrl, config) {
+  navigator.serviceWorker
+    .register(swUrl)
+    .then(registration => {
+      registration.onupdatefound = () => {
+        const installingWorker = registration.installing
+        if (installingWorker == null) {
+          return
+        }
+        installingWorker.onstatechange = () => {
+          if (installingWorker.state === 'installed') {
+            if (navigator.serviceWorker.controller) {
+              // At this point, the updated precached content has been fetched,
+              // but the previous service worker will still serve the older
+              // content until all client tabs are closed.
+              console.log(
+                'New content is available and will be used when all ' +
+                  'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+              )
+
+              // Execute callback
+              if (config && config.onUpdate) {
+                config.onUpdate(registration)
+              }
+            } else {
+              // At this point, everything has been precached.
+              // It's the perfect time to display a
+              // "Content is cached for offline use." message.
+              // console.log('Content is cached for offline use.');
+
+              // Execute callback
+              if (config && config.onSuccess) {
+                config.onSuccess(registration)
+              }
+            }
+          }
+        }
+      }
+    })
+    .catch(error => {
+      console.error('Error during service worker registration:', error)
+    })
+}
+
+function checkValidServiceWorker(swUrl, config) {
+  // Check if the service worker can be found. If it can't reload the page.
+  fetch(swUrl)
+    .then(response => {
+      // Ensure service worker exists, and that we really are getting a JS file.
+      const contentType = response.headers.get('content-type')
+      if (
+        response.status === 404 ||
+        (contentType != null && contentType.indexOf('javascript') === -1)
+      ) {
+        // No service worker found. Probably a different app. Reload the page.
+        navigator.serviceWorker.ready.then(registration => {
+          registration.unregister().then(() => {
+            window.location.reload()
+          })
+        })
+      } else {
+        // Service worker found. Proceed as normal.
+        registerValidSW(swUrl, config)
+      }
+    })
+    .catch(() => {
+      console.log(
+        'No internet connection found. App is running in offline mode.'
+      )
+    })
+}
+
+export function unregister() {
+  if ('serviceWorker' in navigator) {
+    navigator.serviceWorker.ready.then(registration => {
+      registration.unregister()
+    })
+  }
+}

+ 12 - 0
src/setupProxy.js

@@ -0,0 +1,12 @@
+const proxy = require("http-proxy-middleware");
+
+module.exports = function(app) {
+  // proxy websocket
+  app.use(
+    proxy("/socket.io", {
+      target: "http://localhost:3500",
+      changeOrigin: true,
+      ws: true
+    })
+  );
+};

+ 7 - 0
src/socket.js

@@ -0,0 +1,7 @@
+import io from "socket.io-client";
+
+const socket_location = `${process.env.PUBLIC_URL}/socket.io`;
+const socket = io(window.location.origin, { path: socket_location });
+console.log(`socket location: ${window.location.origin + socket_location}`);
+
+export default socket;