Merge branch 'next-ui-select' of https://github.com/coralproject/talk into next-ui-select

This commit is contained in:
Chi Vinh Le
2018-10-17 15:48:30 +02:00
13 changed files with 173 additions and 16 deletions
+18
View File
@@ -1772,6 +1772,15 @@
"integrity": "sha512-p+gNRe4RPjpl1lTBUomFJ42P8ymArH/P93DFJ0iY873BJ4ZmogcKc6TbHgZQmtQMsy3jxcAo0HcTjidXwo8uKg==",
"dev": true
},
"@types/cors": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.4.tgz",
"integrity": "sha512-ipZjBVsm2tF/n8qFGOuGBkUij9X9ZswVi9G3bx/6dz7POpVa6gVHcj1wsX/LVEn9MMF41fxK/PnZPPoTD1UFPw==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/cross-spawn": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.0.tgz",
@@ -6861,6 +6870,15 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz",
"integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"cosmiconfig": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-3.1.0.tgz",
+2
View File
@@ -54,6 +54,7 @@
"cheerio": "^1.0.0-rc.2",
"consolidate": "0.14.0",
"convict": "^4.3.1",
"cors": "^2.8.4",
"dataloader": "^1.4.0",
"dotenv": "^6.0.0",
"dotenv-expand": "^4.2.0",
@@ -119,6 +120,7 @@
"@types/compression-webpack-plugin": "^0.4.2",
"@types/consolidate": "0.0.34",
"@types/convict": "^4.2.0",
"@types/cors": "^2.8.4",
"@types/cross-spawn": "^6.0.0",
"@types/dompurify": "0.0.31",
"@types/dotenv": "^4.0.3",
+21
View File
@@ -9,6 +9,7 @@ import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
import UglifyJsPlugin from "uglifyjs-webpack-plugin";
import webpack, { Configuration } from "webpack";
import ManifestPlugin from "webpack-manifest-plugin";
import PublicURIWebpackPlugin from "./plugins/PublicURIWebpackPlugin";
import paths from "./paths";
@@ -39,6 +40,13 @@ export default function createWebpackConfig({
const isProduction = env.NODE_ENV === "production";
/**
* ifProduction will only include the nodes if we're in production mode.
*/
const ifProduction = isProduction
? <T extends {}>(...nodes: T[]) => nodes
: <T extends {}>(...nodes: T[]) => [];
const htmlWebpackConfig: Options = {
minify: isProduction && {
removeComments: true,
@@ -438,12 +446,14 @@ export default function createWebpackConfig({
stream: [
// We ship polyfills by default
paths.appPolyfill,
...ifProduction(paths.appPublicPath),
...devServerEntries,
paths.appStreamIndex,
],
auth: [
// We ship polyfills by default
paths.appPolyfill,
...ifProduction(paths.appPublicPath),
...devServerEntries,
paths.appAuthIndex,
// Remove deactivated entries.
@@ -451,12 +461,14 @@ export default function createWebpackConfig({
install: [
// We ship polyfills by default
paths.appPolyfill,
...ifProduction(paths.appPublicPath),
...devServerEntries,
paths.appInstallIndex,
],
admin: [
// We ship polyfills by default
paths.appPolyfill,
...ifProduction(paths.appPublicPath),
...devServerEntries,
paths.appAdminIndex,
],
@@ -495,6 +507,15 @@ export default function createWebpackConfig({
inject: "body",
...htmlWebpackConfig,
}),
...ifProduction(
// Inject the pieces we need here to resolve all the now relative url's
// against the CDN if it's provided. It will inject the following into
// the configuration blob on the page.
new PublicURIWebpackPlugin(
"{{ staticURI | dump | safe }}",
"{{ staticURI }}"
)
),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
+1
View File
@@ -17,6 +17,7 @@ export default {
appSrc: resolveSrc("."),
appTsconfig: resolveSrc("core/client/tsconfig.json"),
appPolyfill: resolveSrc("core/build/polyfills.js"),
appPublicPath: resolveSrc("core/build/publicPath.js"),
appLocales: resolveSrc("locales"),
appThemeVariables: resolveSrc("core/client/ui/theme/variables.ts"),
appThemeVariablesCSS: resolveSrc("core/client/ui/theme/variables.css"),
@@ -0,0 +1,60 @@
import { Hooks } from "html-webpack-plugin";
import { Compiler, Plugin } from "webpack";
export default class PublicURIWebpackPlugin implements Plugin {
private configTemplate: string;
private prefixTemplate: string;
constructor(configTemplate: string, prefixTemplate: string) {
this.configTemplate = configTemplate;
this.prefixTemplate = prefixTemplate;
}
private prefixAttribute(attr: string | boolean) {
if (!attr || typeof attr !== "string" || !attr.startsWith("/")) {
return attr;
}
return this.prefixTemplate + attr;
}
private prefixTag = (tag: {
tagName: string;
attributes: Record<string, string | boolean>;
}) => {
switch (tag.tagName) {
case "link":
tag.attributes.href = this.prefixAttribute(tag.attributes.href);
break;
case "script":
tag.attributes.src = this.prefixAttribute(tag.attributes.src);
break;
}
};
public apply = (compiler: Compiler) => {
compiler.hooks.compilation.tap("CDNWebpackPlugin", compilation => {
(compilation.hooks as Hooks).htmlWebpackPluginAlterAssetTags.tapAsync(
"CDNWebpackPlugin",
(htmlPluginData, cb) => {
// Prefix all the asset's url's with the template.
htmlPluginData.head.forEach(this.prefixTag);
htmlPluginData.body.forEach(this.prefixTag);
// Insert the public path reference.
htmlPluginData.body.unshift({
tagName: "script",
attributes: {
type: "application/json",
id: "config",
},
innerHTML: this.configTemplate,
voidTag: false,
});
return cb(null, htmlPluginData);
}
);
});
};
}
+3
View File
@@ -0,0 +1,3 @@
__webpack_public_path__ = JSON.parse(
document.getElementById("config").innerText
);
@@ -1,5 +1,4 @@
.buttonReset {
/* reset button */
user-select: none;
font-family: inherit;
@@ -28,7 +27,6 @@
}
&:-moz-focusring {
color: transparent;
textshadow: 0 0 0 #000;
}
}
+24
View File
@@ -27,6 +27,20 @@ convict.addFormat({
},
});
// Add a custom format for the optional-url.
convict.addFormat({
name: "optional-url",
validate: (url: string) => {
if (url) {
Joi.assert(url, Joi.string().uri());
}
},
// Ensure that there is no ending slash.
coerce: (url: string) => {
return url.replace(/\/$/, "");
},
});
const config = convict({
env: {
doc: "The application environment.",
@@ -54,6 +68,7 @@ const config = convict({
default: "mongodb://127.0.0.1:27017/talk",
env: "MONGODB_URI",
arg: "mongodb",
sensitive: true,
},
redis: {
doc: "The Redis database to connect to.",
@@ -61,6 +76,7 @@ const config = convict({
default: "redis://127.0.0.1:6379",
env: "REDIS_URI",
arg: "redis",
sensitive: true,
},
signing_secret: {
doc: "",
@@ -68,6 +84,7 @@ const config = convict({
default: "keyboard cat", // TODO: (wyattjoh) evaluate best solution
env: "SIGNING_SECRET",
arg: "signingSecret",
sensitive: true,
},
signing_algorithm: {
doc: "",
@@ -93,6 +110,13 @@ const config = convict({
env: "LOGGING_LEVEL",
arg: "logging",
},
static_uri: {
doc: "The URL that static assets will be hosted from",
format: "optional-url",
default: "",
env: "STATIC_URI",
arg: "staticUri",
},
disable_tenant_caching: {
doc:
"Disables the tenant caching, all tenants will be loaded from MongoDB each time it's needed",
+4
View File
@@ -1,4 +1,5 @@
import cons from "consolidate";
import cors from "cors";
import { Express } from "express";
import http from "http";
import { Redis } from "ioredis";
@@ -56,6 +57,9 @@ export async function createApp(options: AppOptions): Promise<Express> {
})
);
// Enable CORS headers for media assets, font's require them.
parent.use("/assets/media", cors());
// Static Files
parent.use("/assets", cacheHeadersMiddleware("1w"), serveStatic);
@@ -1,7 +1,7 @@
import { RequestHandler } from "express";
import ms from "ms";
export const nocacheMiddleware: RequestHandler = (req, res, next) => {
export const noCacheMiddleware: RequestHandler = (req, res, next) => {
// Set cache control headers to prevent browsers/cdn's from caching these
// requests.
res.set({ "Cache-Control": "no-cache, no-store, must-revalidate" });
@@ -9,10 +9,12 @@ export const nocacheMiddleware: RequestHandler = (req, res, next) => {
next();
};
export const cacheHeadersMiddleware = (duration?: string): RequestHandler => {
export const cacheHeadersMiddleware = (
duration?: string | false
): RequestHandler => {
const maxAge = duration ? Math.floor(ms(duration) / 1000) : false;
if (!maxAge) {
return nocacheMiddleware;
return noCacheMiddleware;
}
return (req, res, next) => {
+9 -2
View File
@@ -11,10 +11,17 @@ export interface ClientTargetHandlerOptions {
/**
* cacheDuration is the cache duration that a given request should be cached for.
*/
cacheDuration?: string;
cacheDuration?: string | false;
/**
* staticURI is prepended to the static url's that are included on the static
* pages.
*/
staticURI: string;
}
export function createClientTargetRouter({
staticURI,
view,
cacheDuration = "1h",
}: ClientTargetHandlerOptions) {
@@ -22,7 +29,7 @@ export function createClientTargetRouter({
const router = express.Router();
router.get("/", cacheHeadersMiddleware(cacheDuration), (req, res) =>
res.render(view)
res.render(view, { staticURI })
);
return router;
+23 -9
View File
@@ -1,7 +1,7 @@
import express, { Router } from "express";
import { AppOptions } from "talk-server/app";
import { nocacheMiddleware } from "talk-server/app/middleware/cacheHeaders";
import { noCacheMiddleware } from "talk-server/app/middleware/cacheHeaders";
import { installedMiddleware } from "talk-server/app/middleware/installed";
import playground from "talk-server/app/middleware/playground";
import { RouterOptions } from "talk-server/app/router/types";
@@ -14,42 +14,56 @@ export async function createRouter(app: AppOptions, options: RouterOptions) {
// Create a router.
const router = express.Router();
router.use("/api", nocacheMiddleware, await createAPIRouter(app, options));
router.use("/api", noCacheMiddleware, await createAPIRouter(app, options));
// Attach the GraphiQL if enabled.
if (app.config.get("enable_graphiql")) {
attachGraphiQL(router, app);
}
const staticURI = app.config.get("static_uri");
// Add the embed targets.
router.use("/embed/stream", createClientTargetRouter({ view: "stream" }));
router.use("/embed/auth", createClientTargetRouter({ view: "auth" }));
router.use(
"/embed/stream",
createClientTargetRouter({ staticURI, view: "stream" })
);
router.use(
"/embed/auth",
createClientTargetRouter({ staticURI, view: "auth", cacheDuration: false })
);
// Add the standalone targets.
router.use(
"/admin",
// If we aren't already installed, redirect the user to the install page.
installedMiddleware({
tenantCache: app.tenantCache,
}),
createClientTargetRouter({ view: "admin" })
createClientTargetRouter({ staticURI, view: "admin", cacheDuration: false })
);
router.use(
"/install",
// If we're already installed, redirect the user to the admin page.
installedMiddleware({
tenantCache: app.tenantCache,
redirectIfInstalled: true,
redirectURL: "/admin",
}),
createClientTargetRouter({ view: "install", cacheDuration: "" })
createClientTargetRouter({
staticURI,
view: "install",
cacheDuration: false,
})
);
// Handle the root path.
router.get(
"/",
// Redirect the user to the install page if they are not, otherwise redirect
// them to the admin.
installedMiddleware({ tenantCache: app.tenantCache }),
(req, res, next) => {
res.redirect("/admin");
}
(req, res, next) => res.redirect("/admin")
);
return router;
+3
View File
@@ -38,9 +38,12 @@ class Server {
constructor(options: ServerOptions) {
this.parentApp = express();
// Load the configuration.
this.config = config
.load(options.config || {})
.validate({ allowed: "strict" });
logger.debug({ config: this.config.toString() }, "loaded configuration");
// Load the graph schemas.
this.schemas = {