Skip to content

Commit

Permalink
major refactor / cleanup / framework-ification
Browse files Browse the repository at this point in the history
  • Loading branch information
floodfx committed Jan 24, 2022
1 parent 0fca22a commit d709d40
Show file tree
Hide file tree
Showing 25 changed files with 351 additions and 186 deletions.
19 changes: 16 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,23 @@
"name": "liveviewjs-poc",
"version": "1.0.0",
"description": "",
"client": "dist/client/liveview.js",
"server": "dist/server/index.js",
"targets": {
"client": {
"source": "src/client/liveview.ts",
"context": "browser"
},
"server": {
"source": "./src/server/index.ts",
"context": "node",
"isLibrary": true
}
},
"scripts": {
"server-build": "tsc --watch -p tsconfig.json",
"server": "nodemon -e js -w dist dist/socket/websocket_server.js",
"client-build": "parcel build src/client/*.ts --dist-dir dist/client",
"run": "nodemon -e js -w dist dist/examples/index.js",
"build": "parcel build",
"watch": "parcel watch",
"test": "jest --expand"
},
"keywords": [],
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import escapeHtml from "../../liveview/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "src/server/liveview/types";
import { PhxSocket } from "../../socket/types";
import { listCities, suggest } from "./data";
import { sendInternalMessage } from "../../socket/websocket_server";
import escapeHtml from "../../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../../server/types";
import { PhxSocket } from "../../server/socket/types";
import { sendInternalMessage } from "../../server/socket/message_router";
import { WebSocket } from "ws";
import { searchByCity, searchByZip, Store } from "../live-search/data";
import { listCities, suggest } from "./data";


export interface AutocompleteContext {
Expand Down
File renamed without changes.
File renamed without changes.
30 changes: 30 additions & 0 deletions src/examples/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { LiveViewServer } from '../server';
import { LiveViewRouter } from '../server/types';
import { AutocompleteLiveViewComponent } from './autocomplete/component';
import { LicenseLiveViewComponent } from './license_liveview';
import { LightLiveViewComponent } from './light_liveview';
import { SearchLiveViewComponent } from './live-search/component';
import { SalesDashboardLiveViewComponent } from './sales_dashboard_liveview';

const lvServer = new LiveViewServer({
// port: 3002,
// rootView: "./examples/rootView.ejs",
// viewsPath: "./examples/views",
// support different templates?
});

export const router: LiveViewRouter = {
"/license": new LicenseLiveViewComponent(),
'/sales-dashboard': new SalesDashboardLiveViewComponent(),
'/search': new SearchLiveViewComponent(),
"/autocomplete": new AutocompleteLiveViewComponent(),
}

// register all routes
lvServer.registerLiveViewRoutes(router)

// register single route
lvServer.registerLiveViewRoute("/light", new LightLiveViewComponent())

// start server
lvServer.start();
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import escapeHtml from "../liveview/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener } from "../liveview/types";
import { PhxSocket } from "../socket/types";
import { numberToCurrency } from "../utils";
import escapeHtml from "../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../server/types";
import { PhxSocket } from "../server/socket/types";
import { numberToCurrency } from "./utils";
import { LightContext } from "./light_liveview";

export interface LicenseContext {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import escapeHtml from "../liveview/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener } from "../liveview/types";
import { PhxSocket } from "../socket/types";
import escapeHtml from "../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../server/types";
import { PhxSocket } from "../server/socket/types";

export interface LightContext {
brightness: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import escapeHtml from "../../liveview/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "src/server/liveview/types";
import { PhxSocket } from "../../socket/types";
import { listStores, searchByZip, Store } from "./data";
import { sendInternalMessage } from "../../socket/websocket_server";
import escapeHtml from "../../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../../server/types";
import { PhxSocket } from "../../server/socket/types";
import { sendInternalMessage } from "../../server/socket/message_router";
import { WebSocket } from "ws";

import { searchByZip, Store } from "./data";


export interface SearchContext {
zip: string;
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import escapeHtml from "../liveview/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../liveview/types";
import { PhxSocket } from "../socket/types";
import { sendInternalMessage } from "../socket/websocket_server";
import { numberToCurrency } from "../utils";
import escapeHtml from "../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../server/types";
import { PhxSocket } from "../server/socket/types";
import { numberToCurrency } from "./utils";
import { sendInternalMessage } from "../server/socket/message_router";

// generate a random number between min and max
const random = (min: number, max: number): () => number => {
Expand Down
11 changes: 11 additions & 0 deletions src/examples/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";
exports.__esModule = true;
exports.numberToCurrency = void 0;
function numberToCurrency(amount) {
var formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
return formatter.format(amount);
}
exports.numberToCurrency = numberToCurrency;
File renamed without changes.
94 changes: 1 addition & 93 deletions src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1 @@
import "reflect-metadata";
import path from "path"
import express, { Request, Response } from 'express'
import { router } from "./live/router";
import jwt from "jsonwebtoken";
import session, {MemoryStore} from "express-session";
import { nanoid } from "nanoid";
import { PhxSocket } from "./socket/types";
import { Server } from "http";


const app = express();

// TODO replace me with your own secret
export const SIGNING_SECRET = "blah_secret";
export const sessionStore = new MemoryStore();

const publicPath = path.join(__dirname, "..", "dist", "client");
const viewsPath = path.join(__dirname, "..", "src", "server", "views");

app.set('view engine', 'ejs');
app.set("views", viewsPath)
const server = new Server(app);

app.use((req: Request, res: Response, next: Function) => {
console.log("req.url", req.url);
// console.log("req.path", req.path);
next();
});

app.use(express.static(publicPath))

// configure session storage
app.use(session({
secret: SIGNING_SECRET,
resave: false,
rolling: true,
saveUninitialized: true,
cookie: {secure: process.env.NODE_ENV === "production"},
store: sessionStore,
}))
// extend / define session interface
declare module 'express-session' {
interface SessionData {
csrfToken: string;
}
}

console.log("router", router);

// register each route path to the component to be rendered
Object.keys(router).forEach(key => {
console.log("register route", key);
app.get(key, (req: Request, res: Response) => {
console.log("key", key);
// new LiveViewId per HTTP requess?
const liveViewId = nanoid();
const phxSocket: PhxSocket = {
id: liveViewId,
connected: false, // http request
}

// render the component
const component = router[key];
const ctx = component.mount({}, {}, phxSocket);
const view = component.render(ctx);

// lookup / gen csrf token for this session
if(!req.session.csrfToken) {
req.session.csrfToken = nanoid();
}

// render the view with all the data
res.render("index", {
page_title: "Live View",
csrf_meta_tag: req.session.csrfToken,
liveViewId,
session: jwt.sign(JSON.stringify(req.session), SIGNING_SECRET),
statics: jwt.sign(JSON.stringify(view.statics), SIGNING_SECRET),
inner_content: view.toString()
})

})
})

export {app};

// const port: number = 3002

// server.listen(port, function () {
// console.log(`App is listening on port ${port} !`)
// })

export * from "./live_view_server";
14 changes: 0 additions & 14 deletions src/server/live/router.ts

This file was deleted.

141 changes: 141 additions & 0 deletions src/server/live_view_server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { LiveViewComponent, LiveViewRouter } from "./types";
import WebSocket from 'ws';
import http, { Server, createServer } from 'http';
import express from "express";
import { PhxSocket } from "./socket/types";
import { nanoid } from "nanoid";
import jwt from "jsonwebtoken";
import { onMessage } from "./socket/message_router";
import session, { MemoryStore } from "express-session";
import path from "path";

// extend / define session interface
declare module 'express-session' {
interface SessionData {
csrfToken: string;
}
}

export interface LiveViewServerOptions {
port: number;
rootView: string;
viewsPath: string;
signingSecret: string;
sessionStore: session.Store;
}

export class LiveViewServer {
private port: number = 4444;
private rootView: string = "index.html.ejs";
private viewsPath: string = path.join(__dirname, "..", "..", "src", "server", "web", "views");
private signingSecret: string = nanoid();
private sessionStore:session.Store = new MemoryStore();

private _router: LiveViewRouter = {};

private _topicToPath: { [key: string]: string } = {};

constructor(options: Partial<LiveViewServerOptions>) {
this.port = options.port ?? this.port;
this.rootView = options.rootView ?? this.rootView;
this.viewsPath = options.viewsPath ?? this.viewsPath;
this.signingSecret = options.signingSecret ?? this.signingSecret;
this.sessionStore = options.sessionStore ?? this.sessionStore;
}

registerLiveViewRoutes(router: LiveViewRouter) {
this._router = { ...this._router, ...router };
}

registerLiveViewRoute(path: string, component: LiveViewComponent<any>) {
this._router[path] = component;
}

start() {
console.log("starting")
const app = this.buildExpressApp();

const httpServer = new Server();
const wsServer = new WebSocket.Server({
server: httpServer
});

// register express app for http requests
httpServer.on('request', app);

// register websocket server ws requests
wsServer.on('connection', socket => {
// handle ws messages
socket.on('message', message => {
onMessage(socket, message, this._topicToPath, this._router);
});
});

httpServer.listen(this.port, () => {
console.log(`LiveView App is listening on port ${this.port} !`)
})
}

private buildExpressApp() {
const app = express();

const publicPath = path.join(__dirname, "..", "client");
// console.log("publicPath", publicPath);

app.use(express.static(publicPath))

app.set('view engine', 'ejs');
app.set("views", this.viewsPath)

app.use(session({
secret: this.signingSecret,
resave: false,
rolling: true,
saveUninitialized: true,
cookie: {secure: process.env.NODE_ENV === "production"},
store: this.sessionStore,
}))



app.get('/:liveview', (req, res) => {
const liveview = req.params.liveview;
console.log("liveview", liveview);

// new LiveViewId per HTTP requess?
const liveViewId = nanoid(); // TODO allow option for id generator?
const phxSocket: PhxSocket = {
id: liveViewId,
connected: false, // ws socket not connected on http request
}

// look up component for route
const component = this._router[`/${liveview}`];
if (!component) {
res.status(404).send("Not found");
return;
}

// mount and render component if found
const ctx = component.mount({}, {}, phxSocket);
const view = component.render(ctx);

// lookup / gen csrf token for this session
if(!req.session.csrfToken) {
req.session.csrfToken = nanoid();
}

// render the view with all the data
res.render("index.html.ejs", {
page_title: "Live View",
csrf_meta_tag: req.session.csrfToken,
liveViewId,
session: jwt.sign(JSON.stringify(req.session), this.signingSecret),
statics: jwt.sign(JSON.stringify(view.statics), this.signingSecret),
inner_content: view.toString()
})
});

return app;
}
}
Loading

0 comments on commit d709d40

Please sign in to comment.