diff --git a/out/auth-proxy.d.ts b/out/auth-proxy.d.ts
new file mode 100644
index 0000000..68ae7b7
--- /dev/null
+++ b/out/auth-proxy.d.ts
@@ -0,0 +1,5 @@
+///
+import { RequestHandler } from 'express';
+export declare const AuthProxy: {
+ router: RequestHandler;
+};
diff --git a/out/auth-proxy.js b/out/auth-proxy.js
new file mode 100644
index 0000000..20b53c8
--- /dev/null
+++ b/out/auth-proxy.js
@@ -0,0 +1,49 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.AuthProxy = void 0;
+const _1 = require(".");
+const node_fetch_1 = require("node-fetch");
+const router = (req, res, next) => {
+ const resolvable = new _1.Resolvable(() => __awaiter(void 0, void 0, void 0, function* () {
+ if (!_1.DefaultConfig.USERINFO_HEADER) {
+ return undefined;
+ }
+ const token = req.header(_1.DefaultConfig.USERINFO_HEADER);
+ const url = _1.DefaultConfig.AUTH_PROXY_USERINFO_URL ||
+ _1.DefaultConfig.AUTH_PROXY_URL && _1.urlJoin(_1.DefaultConfig.AUTH_PROXY_URL, "userinfo");
+ if (token === undefined || url === undefined) {
+ return undefined;
+ }
+ try {
+ const res = yield node_fetch_1.default(url, { headers: [[_1.DefaultConfig.USERINFO_HEADER, token]] });
+ return yield res.json();
+ }
+ catch (e) {
+ _1.Logger.warn(e);
+ return undefined;
+ }
+ }));
+ req.getUserInfo = () => resolvable.resolve();
+ res.initLogout = function () {
+ const url = _1.DefaultConfig.AUTH_PROXY_INIT_LOGOUT_URL ||
+ _1.DefaultConfig.AUTH_PROXY_URL && _1.urlJoin(_1.DefaultConfig.AUTH_PROXY_URL, "init-logout");
+ if (url === undefined) {
+ return false;
+ }
+ this.redirect(307, url);
+ return true;
+ };
+ next();
+};
+exports.AuthProxy = {
+ router,
+};
diff --git a/out/auto-reload.d.ts b/out/auto-reload.d.ts
new file mode 100644
index 0000000..9586b23
--- /dev/null
+++ b/out/auto-reload.d.ts
@@ -0,0 +1,3 @@
+export declare const AutoReloader: {
+ router: import("express-serve-static-core").Router;
+};
diff --git a/out/auto-reload.js b/out/auto-reload.js
new file mode 100644
index 0000000..1fdbfc9
--- /dev/null
+++ b/out/auto-reload.js
@@ -0,0 +1,61 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.AutoReloader = void 0;
+const express_1 = require("express");
+const _1 = require(".");
+const uuid_1 = require("uuid");
+const router = express_1.Router();
+if (!_1.DefaultConfig.isProduction) {
+ let uuid = uuid_1.v4();
+ let updateTimeout = undefined;
+ Promise.resolve().then(() => require("node-watch")).then((watch) => {
+ watch.default('public', { recursive: true }, () => {
+ if (updateTimeout !== undefined)
+ clearTimeout(updateTimeout);
+ updateTimeout = setTimeout(() => {
+ uuid = uuid_1.v4();
+ }, 200);
+ });
+ }).catch((err) => { _1.Logger.error(err); });
+ router.get("/auto-reload/client.js", (req, res) => {
+ _1.Logger.debug(req.url, req.originalUrl, req.baseUrl);
+ res.setHeader('Content-Type', "application/javascript");
+ // language=JavaScript
+ res.send(`
+ const loc = window.location;
+ const url = loc.protocol+'//'+loc.host+'${_1.urlJoin(req.baseUrl, "/auto-reload")}';
+ // const parse = async res => (await res.json()).uuid;
+ const parse = function(res) {
+ return res.json()
+ .then(function(json) {return json.uuid;}) }
+ let hash = undefined;
+ let hadError = false;
+ setInterval(function() {
+ try {
+ fetch(url)
+ .then(function(res) { return parse(res) })
+ .then(function(data) {
+ if (data) {
+ hash = hash === undefined ? data : hash;
+ if (hash !== data) {
+ window.location.reload();
+ }
+ }
+ });
+ } catch (e) {
+ if (hadError === false) {
+ console.log(e);
+ hadError = true;
+ }
+ }
+ }, 3000);
+ `.replace(/\t/g, ""));
+ });
+ router.get("/auto-reload", (req, res) => {
+ req.noLogging = true;
+ res.json({ uuid });
+ });
+}
+exports.AutoReloader = {
+ router,
+};
diff --git a/out/config.d.ts b/out/config.d.ts
new file mode 100644
index 0000000..e3b7bef
--- /dev/null
+++ b/out/config.d.ts
@@ -0,0 +1,17 @@
+declare function requireEnv(name: string, onlyInProduction?: boolean): void;
+export declare const DefaultConfig: {
+ EXTERNAL_BASE_URL: string;
+ isProduction: boolean;
+ requireEnv: typeof requireEnv;
+ NODE_ENV: string;
+ PORT: number;
+ HOSTNAME: string;
+ BASE_PATH: string;
+ REDIS_URL: string | undefined;
+ SESSION_SECRET: string | undefined;
+ USERINFO_HEADER: string | undefined;
+ AUTH_PROXY_URL: string | undefined;
+ AUTH_PROXY_USERINFO_URL: string | undefined;
+ AUTH_PROXY_INIT_LOGOUT_URL: string | undefined;
+};
+export {};
diff --git a/out/config.js b/out/config.js
new file mode 100644
index 0000000..dcced38
--- /dev/null
+++ b/out/config.js
@@ -0,0 +1,36 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DefaultConfig = void 0;
+const env = require("env-var");
+const _1 = require(".");
+const NODE_ENV = env.get('NODE_ENV').default("development").asString();
+const isProduction = NODE_ENV === 'production';
+const envs = {
+ NODE_ENV,
+ // port of the server
+ PORT: env.get('PORT').default('3000').asPortNumber(),
+ // hostname of the server ('0.0.0.0' listens on all network interfaces)
+ HOSTNAME: env.get('HOSTNAME').default('0.0.0.0').asString(),
+ // base path
+ BASE_PATH: env.get('BASE_PATH').default('/').asString(),
+ // external base url
+ EXTERNAL_BASE_URL: env.get('EXTERNAL_BASE_URL').asString(),
+ // url of redis session store (required in production using InMemory)
+ REDIS_URL: env.get('REDIS_URL').asString() || undefined,
+ // cookie secret for the session id (required in production using Session)
+ SESSION_SECRET: env.get('SESSION_SECRET').asString() || undefined,
+ // header where user info token is stored to request auth proxy
+ USERINFO_HEADER: env.get('USERINFO_HEADER').asString() || undefined,
+ // base url to init a logout or request user info
+ AUTH_PROXY_URL: env.get('AUTH_PROXY_URL').asString() || undefined,
+ // override base url to request user info
+ AUTH_PROXY_USERINFO_URL: env.get('AUTH_PROXY_USERINFO_URL').asString() || undefined,
+ // override base url to init a logout
+ AUTH_PROXY_INIT_LOGOUT_URL: env.get('AUTH_PROXY_INIT_LOGOUT_URL').asString() || undefined,
+};
+function requireEnv(name, onlyInProduction = false) {
+ env.get(name).required(!onlyInProduction || isProduction).asString();
+}
+exports.DefaultConfig = Object.assign(Object.assign({}, envs), { EXTERNAL_BASE_URL: envs.EXTERNAL_BASE_URL ||
+ _1.urlJoin(`http://${envs.HOSTNAME}${envs.PORT !== 80 ? `:${envs.PORT}` : ""}`, envs.BASE_PATH), isProduction,
+ requireEnv });
diff --git a/out/helpers/resolvable.d.ts b/out/helpers/resolvable.d.ts
new file mode 100644
index 0000000..b5bec50
--- /dev/null
+++ b/out/helpers/resolvable.d.ts
@@ -0,0 +1,27 @@
+declare enum ResolvableState {
+ WAITING = 0,
+ PENDING = 1,
+ ERROR = 2,
+ DONE = 3
+}
+declare class FetchOnce> {
+ protected fetchMethod?: ((...args: U) => Promise) | undefined;
+ protected data: T | undefined;
+ protected error: unknown | undefined;
+ protected state: ResolvableState;
+ protected pendings: [(res: Promise | T) => void, (reason: unknown) => void][];
+ constructor(fetchMethod?: ((...args: U) => Promise) | undefined);
+ resolve(...args: U): Promise;
+ protected isFinished(): boolean;
+ protected parsePromise(promise: Promise): void;
+}
+export declare class Resolvable> extends FetchOnce {
+ constructor(fetchMethod: (...args: U) => Promise);
+}
+export declare class WaitForSync extends FetchOnce {
+ protected state: ResolvableState;
+ constructor();
+ setData(data: T): void;
+ setError(error: unknown): void;
+}
+export {};
diff --git a/out/helpers/resolvable.js b/out/helpers/resolvable.js
new file mode 100644
index 0000000..c155b3c
--- /dev/null
+++ b/out/helpers/resolvable.js
@@ -0,0 +1,85 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.WaitForSync = exports.Resolvable = void 0;
+var ResolvableState;
+(function (ResolvableState) {
+ ResolvableState[ResolvableState["WAITING"] = 0] = "WAITING";
+ ResolvableState[ResolvableState["PENDING"] = 1] = "PENDING";
+ ResolvableState[ResolvableState["ERROR"] = 2] = "ERROR";
+ ResolvableState[ResolvableState["DONE"] = 3] = "DONE";
+})(ResolvableState || (ResolvableState = {}));
+class FetchOnce {
+ constructor(fetchMethod) {
+ this.fetchMethod = fetchMethod;
+ this.state = ResolvableState.WAITING;
+ this.pendings = [];
+ }
+ resolve(...args) {
+ // eslint-disable-next-line promise/avoid-new
+ return new Promise((resolve, reject) => {
+ switch (this.state) {
+ case ResolvableState.WAITING:
+ this.state = ResolvableState.PENDING;
+ this.pendings.push([resolve, reject]);
+ if (this.fetchMethod)
+ this.parsePromise(this.fetchMethod(...args));
+ break;
+ case ResolvableState.PENDING:
+ this.pendings.push([resolve, reject]);
+ break;
+ case ResolvableState.DONE:
+ resolve(this.data);
+ break;
+ case ResolvableState.ERROR:
+ reject(this.error);
+ break;
+ }
+ });
+ }
+ isFinished() {
+ return this.state === ResolvableState.DONE || this.state === ResolvableState.ERROR;
+ }
+ parsePromise(promise) {
+ promise.then((data) => {
+ this.data = data;
+ this.state = ResolvableState.DONE;
+ this.pendings.forEach(pending => pending[0](data));
+ }).catch(err => {
+ this.error = err;
+ this.state = ResolvableState.ERROR;
+ this.pendings.forEach(pending => pending[1](err));
+ });
+ }
+}
+class Resolvable extends FetchOnce {
+ constructor(fetchMethod) {
+ super(fetchMethod);
+ }
+}
+exports.Resolvable = Resolvable;
+class WaitForSync extends FetchOnce {
+ constructor() {
+ super(undefined);
+ this.state = ResolvableState.PENDING;
+ }
+ setData(data) {
+ if (!this.isFinished()) {
+ this.parsePromise((() => __awaiter(this, void 0, void 0, function* () { return data; }))());
+ }
+ }
+ setError(error) {
+ if (!this.isFinished()) {
+ this.parsePromise((() => __awaiter(this, void 0, void 0, function* () { throw error; }))());
+ }
+ }
+}
+exports.WaitForSync = WaitForSync;
diff --git a/out/helpers/urlJoin.d.ts b/out/helpers/urlJoin.d.ts
new file mode 100644
index 0000000..a972bb6
--- /dev/null
+++ b/out/helpers/urlJoin.d.ts
@@ -0,0 +1,2 @@
+import * as properUrlJoin from 'proper-url-join';
+export declare const urlJoin: properUrlJoin.default;
diff --git a/out/helpers/urlJoin.js b/out/helpers/urlJoin.js
new file mode 100644
index 0000000..2103c9b
--- /dev/null
+++ b/out/helpers/urlJoin.js
@@ -0,0 +1,5 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.urlJoin = void 0;
+const properUrlJoin = require("proper-url-join");
+exports.urlJoin = properUrlJoin;
diff --git a/out/helpers/userinfo.d.ts b/out/helpers/userinfo.d.ts
new file mode 100644
index 0000000..eb054cb
--- /dev/null
+++ b/out/helpers/userinfo.d.ts
@@ -0,0 +1,10 @@
+export declare type UserInfo = {
+ email: string;
+ email_verified: boolean;
+ family_name: string;
+ given_name: string;
+ groups: string[];
+ name: string;
+ preferred_username: string;
+ sub: string;
+};
diff --git a/out/helpers/userinfo.js b/out/helpers/userinfo.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/out/helpers/userinfo.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/out/index.js b/out/index.js
new file mode 100644
index 0000000..9e7d2af
--- /dev/null
+++ b/out/index.js
@@ -0,0 +1,25 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Permissions = exports.Session = exports.Polyfill = exports.AutoReloader = exports.urlJoin = exports.WaitForSync = exports.Resolvable = exports.AuthProxy = exports.HttpLogger = exports.Logger = exports.Redis = exports.DefaultConfig = void 0;
+var config_1 = require("./config");
+Object.defineProperty(exports, "DefaultConfig", { enumerable: true, get: function () { return config_1.DefaultConfig; } });
+var redis_1 = require("./redis");
+Object.defineProperty(exports, "Redis", { enumerable: true, get: function () { return redis_1.Redis; } });
+var logging_1 = require("./logging");
+Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return logging_1.Logger; } });
+Object.defineProperty(exports, "HttpLogger", { enumerable: true, get: function () { return logging_1.HttpLogger; } });
+var auth_proxy_1 = require("./auth-proxy");
+Object.defineProperty(exports, "AuthProxy", { enumerable: true, get: function () { return auth_proxy_1.AuthProxy; } });
+var resolvable_1 = require("./helpers/resolvable");
+Object.defineProperty(exports, "Resolvable", { enumerable: true, get: function () { return resolvable_1.Resolvable; } });
+Object.defineProperty(exports, "WaitForSync", { enumerable: true, get: function () { return resolvable_1.WaitForSync; } });
+var urlJoin_1 = require("./helpers/urlJoin");
+Object.defineProperty(exports, "urlJoin", { enumerable: true, get: function () { return urlJoin_1.urlJoin; } });
+var auto_reload_1 = require("./auto-reload");
+Object.defineProperty(exports, "AutoReloader", { enumerable: true, get: function () { return auto_reload_1.AutoReloader; } });
+var polyfill_1 = require("./polyfill");
+Object.defineProperty(exports, "Polyfill", { enumerable: true, get: function () { return polyfill_1.Polyfill; } });
+var session_1 = require("./session");
+Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_1.Session; } });
+var permissions_1 = require("./permissions");
+Object.defineProperty(exports, "Permissions", { enumerable: true, get: function () { return permissions_1.Permissions; } });
diff --git a/out/logging.d.ts b/out/logging.d.ts
new file mode 100644
index 0000000..f2b5179
--- /dev/null
+++ b/out/logging.d.ts
@@ -0,0 +1,13 @@
+import * as winston from 'winston';
+import { RequestHandler } from 'express';
+export declare const Logger: {
+ error: (...args: unknown[]) => winston.Logger;
+ http: (...args: unknown[]) => winston.Logger;
+ info: (...args: unknown[]) => winston.Logger;
+ silly: (...args: unknown[]) => winston.Logger;
+ warn: (...args: unknown[]) => winston.Logger;
+ verbose: (...args: unknown[]) => winston.Logger;
+ debug: (...args: unknown[]) => winston.Logger;
+ log: (...args: unknown[]) => winston.Logger;
+};
+export declare const HttpLogger: RequestHandler;
diff --git a/out/logging.js b/out/logging.js
new file mode 100644
index 0000000..5509f56
--- /dev/null
+++ b/out/logging.js
@@ -0,0 +1,47 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.HttpLogger = exports.Logger = void 0;
+const winston = require("winston");
+const colors = require("colors");
+const _1 = require(".");
+const prune = require("json-prune");
+const logger = winston.createLogger({
+ level: _1.DefaultConfig.isProduction ? "info" : "silly",
+ format: winston.format.json(),
+ transports: [
+ new winston.transports.Console({
+ format: winston.format.simple(),
+ }),
+ ],
+});
+const levels = ["error", "warn", "info", "http", "verbose", "debug", "silly"];
+const wrapper = (original) => {
+ return (...args) => {
+ return original(args.map((obj) => typeof obj === "string" ? obj : prune(obj)).join(" "));
+ };
+};
+exports.Logger = {};
+for (const level of levels) {
+ exports.Logger[level] = wrapper(logger[level]);
+}
+exports.Logger.log = wrapper(logger["silly"]);
+exports.HttpLogger = (req, res, next) => {
+ const start = Date.now();
+ const path = req.path;
+ const end = res.end;
+ res.end = function (...args) {
+ const statusCode = res.statusCode;
+ const colorFunction = statusCode >= 500 ? colors.red
+ : statusCode >= 400 ? colors.yellow
+ : statusCode >= 300 ? colors.cyan
+ : statusCode >= 200 ? colors.green
+ : colors.gray;
+ const status = colorFunction(res.statusCode.toString(10));
+ const method = req.method.toUpperCase().padEnd(6, " ");
+ const responseTime = (Date.now() - start).toString(10).padStart(3, " ");
+ if (!req.noLogging)
+ exports.Logger.http(`${status} ${method} ${responseTime}ms ${path}`);
+ end.apply(res, args);
+ };
+ next();
+};
diff --git a/out/permissions.d.ts b/out/permissions.d.ts
new file mode 100644
index 0000000..6a1297b
--- /dev/null
+++ b/out/permissions.d.ts
@@ -0,0 +1,24 @@
+import { AccessControl, IQueryInfo, Permission } from 'role-acl';
+import { Query } from 'role-acl/lib/src/core/Query';
+import { Request, RequestHandler } from 'express';
+declare class PermissionManager extends AccessControl {
+ can(roleOrRequest: Request | string | string[] | IQueryInfo): PermQuery;
+ getRouter(resource: string, opts: Partial): RequestHandler;
+}
+export declare type RermRouterOpts = {
+ context: unknown;
+ action: string;
+ skipConditions: boolean;
+};
+export declare class PermQuery extends Query {
+ protected resolveRequest: Request | undefined;
+ constructor(grants: unknown, roleOrRequest: Request | string | string[] | IQueryInfo);
+ on(resource: string, skipConditions?: boolean): Promise;
+ context(context: unknown): PermQuery;
+ skipConditions(value: boolean): PermQuery;
+ with(context: unknown): PermQuery;
+ execute(action: string): PermQuery;
+ sync(): PermQuery;
+}
+export declare const Permissions: PermissionManager;
+export {};
diff --git a/out/permissions.js b/out/permissions.js
new file mode 100644
index 0000000..4178f92
--- /dev/null
+++ b/out/permissions.js
@@ -0,0 +1,92 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Permissions = exports.PermQuery = void 0;
+const role_acl_1 = require("role-acl");
+const Query_1 = require("role-acl/lib/src/core/Query");
+// see https://www.npmjs.com/package/role-acl
+class PermissionManager extends role_acl_1.AccessControl {
+ can(roleOrRequest) {
+ return new PermQuery(this.getGrants(), roleOrRequest);
+ }
+ getRouter(resource, opts) {
+ return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
+ let query = this.can(req);
+ if (opts.context)
+ query = query.context(opts.context);
+ if (opts.action)
+ query = query.execute(opts.action);
+ if (opts.skipConditions)
+ query = query.skipConditions(opts.skipConditions);
+ const permission = yield query.on(resource);
+ if (permission.granted) {
+ req.permissionDetails = permission;
+ next();
+ }
+ else {
+ res.sendStatus(403);
+ }
+ });
+ }
+}
+class PermQuery extends Query_1.Query {
+ constructor(grants, roleOrRequest) {
+ function isRequest(obj) {
+ // eslint-disable-next-line no-prototype-builtins
+ return typeof obj === 'object' && obj && obj.hasOwnProperty('path') || false;
+ }
+ if (isRequest(roleOrRequest)) {
+ super(grants, []);
+ this.resolveRequest = roleOrRequest;
+ }
+ else {
+ super(grants, roleOrRequest);
+ }
+ }
+ on(resource, skipConditions) {
+ const _super = Object.create(null, {
+ on: { get: () => super.on }
+ });
+ var _a;
+ return __awaiter(this, void 0, void 0, function* () {
+ if (this.resolveRequest) {
+ const userInfo = yield this.resolveRequest.getUserInfo();
+ this.role((_a = userInfo === null || userInfo === void 0 ? void 0 : userInfo.groups) !== null && _a !== void 0 ? _a : []);
+ }
+ if (typeof this._.role === 'object' && this._.role.includes('noaccess') ||
+ typeof this._.role === 'string' && this._.role === 'noaccess') {
+ this.role([]);
+ }
+ return _super.on.call(this, resource, skipConditions);
+ });
+ }
+ context(context) {
+ super.context(context);
+ return this;
+ }
+ skipConditions(value) {
+ super.skipConditions(value);
+ return this;
+ }
+ with(context) {
+ super.with(context);
+ return this;
+ }
+ execute(action) {
+ super.execute(action);
+ return this;
+ }
+ sync() {
+ throw new role_acl_1.AccessControlError("Sync method is not allowed on PermissionManager!");
+ }
+}
+exports.PermQuery = PermQuery;
+exports.Permissions = new PermissionManager();
diff --git a/out/polyfill-worker.d.ts b/out/polyfill-worker.d.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/out/polyfill-worker.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/out/polyfill-worker.js b/out/polyfill-worker.js
new file mode 100644
index 0000000..ab2ec1b
--- /dev/null
+++ b/out/polyfill-worker.js
@@ -0,0 +1,25 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+// workers/add.js
+const worker_1 = require("threads/worker");
+const fs = require("fs");
+const polyfill_analyzer_1 = require("@10xjs/polyfill-analyzer");
+const polyfills_1 = require("@10xjs/polyfill-analyzer/dist/polyfills");
+worker_1.expose(() => {
+ const exclude = [
+ "console.markTimeline",
+ "console.timeline",
+ "console.timelineEnd",
+ ];
+ const featureList = polyfill_analyzer_1.analyze({
+ source: fs.readFileSync('./public/js/bundle.js', 'utf-8'),
+ include: polyfills_1.default.filter(x => !exclude.includes(x)),
+ // Not all features listed by polyfillLibrary.listAllPolyfills()` can be detected.
+ unsupportedPolyfill: 'ignore',
+ });
+ const feats = {};
+ for (const feature of featureList) {
+ feats[feature] = {};
+ }
+ return feats;
+});
diff --git a/out/polyfill.js b/out/polyfill.js
new file mode 100644
index 0000000..e1db6ba
--- /dev/null
+++ b/out/polyfill.js
@@ -0,0 +1,39 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Polyfill = void 0;
+const polyfillLibrary = require("polyfill-library");
+const _1 = require(".");
+const threads_1 = require("threads");
+const features = new _1.WaitForSync();
+(() => __awaiter(void 0, void 0, void 0, function* () {
+ const worker = yield threads_1.spawn(new threads_1.Worker("./polyfill-worker"));
+ const feats = yield worker();
+ yield threads_1.Thread.terminate(worker);
+ return feats;
+}))()
+ .then(feats => {
+ feats["fetch"] = {};
+ _1.Logger.debug("Polyfill analysed:", Object.keys(feats));
+ features.setData(feats);
+})
+ .catch(err => features.setError(err));
+function getRouter(opts) {
+ const options = Object.assign({ minify: _1.DefaultConfig.isProduction, unknown: "polyfill" }, opts);
+ return (req, res) => __awaiter(this, void 0, void 0, function* () {
+ const polyfillBundle = yield polyfillLibrary.getPolyfillString(Object.assign(Object.assign({}, options), { uaString: req.header("user-agent"), features: yield features.resolve(), stream: false }));
+ res.setHeader('Content-Type', 'text/javascript');
+ res.send(polyfillBundle);
+ });
+}
+exports.Polyfill = {
+ getRouter: getRouter,
+};
diff --git a/out/redis.d.ts b/out/redis.d.ts
new file mode 100644
index 0000000..e28d55f
--- /dev/null
+++ b/out/redis.d.ts
@@ -0,0 +1,9 @@
+import * as redis from 'redis';
+interface RedisType {
+ client: redis.RedisClient | undefined;
+ get(key: string): Promise;
+ set(key: string, value: string): Promise;
+ del(...keys: string[]): Promise;
+}
+export declare const Redis: RedisType;
+export {};
diff --git a/out/redis.js b/out/redis.js
new file mode 100644
index 0000000..6d0587e
--- /dev/null
+++ b/out/redis.js
@@ -0,0 +1,76 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Redis = void 0;
+const redis = require("redis");
+const _1 = require(".");
+const util_1 = require("util");
+class RealRedis {
+ get client() {
+ if (this._client !== undefined)
+ return this._client;
+ _1.DefaultConfig.requireEnv('REDIS_URL');
+ this._client = redis.createClient({ url: _1.DefaultConfig.REDIS_URL });
+ return this._client;
+ }
+ get(key) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (!this._promGet)
+ this._promGet = util_1.promisify(this.client.get);
+ const value = yield this._promGet(key);
+ // eslint-disable-next-line no-null/no-null
+ return value === null ? undefined : value;
+ });
+ }
+ set(key, value) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (!this._promSet)
+ this._promSet = util_1.promisify(this.client.set);
+ yield this._promSet(key, value);
+ });
+ }
+ del(...keys) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (!this._promDel)
+ this._promDel = util_1.promisify(this.client.del);
+ return yield this._promDel(...keys);
+ });
+ }
+}
+class MockRedis {
+ constructor() {
+ this.inMemory = {};
+ }
+ get client() {
+ return undefined;
+ }
+ get(key) {
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.inMemory[key] || undefined;
+ });
+ }
+ set(key, value) {
+ return __awaiter(this, void 0, void 0, function* () {
+ this.inMemory[key] = value;
+ });
+ }
+ del(...keys) {
+ return __awaiter(this, void 0, void 0, function* () {
+ for (const key of keys) {
+ delete this.inMemory[key];
+ }
+ return keys.length;
+ });
+ }
+}
+exports.Redis = _1.DefaultConfig.REDIS_URL || _1.DefaultConfig.isProduction
+ ? new RealRedis()
+ : new MockRedis();
diff --git a/out/session.d.ts b/out/session.d.ts
new file mode 100644
index 0000000..03fd7d3
--- /dev/null
+++ b/out/session.d.ts
@@ -0,0 +1,7 @@
+import * as session from 'express-session';
+import { RequestHandler } from 'express';
+declare function getRouter(options?: Partial): RequestHandler;
+export declare const Session: {
+ getRouter: typeof getRouter;
+};
+export {};
diff --git a/out/session.js b/out/session.js
new file mode 100644
index 0000000..e7f5889
--- /dev/null
+++ b/out/session.js
@@ -0,0 +1,18 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Session = void 0;
+const session = require("express-session");
+const _1 = require(".");
+const redisStore = require("connect-redis");
+let sessionStore = undefined;
+function getRouter(options) {
+ _1.DefaultConfig.requireEnv('SESSION_SECRET', true);
+ if (_1.Redis.client && sessionStore !== undefined) {
+ const RedisStore = redisStore(session);
+ sessionStore = new RedisStore({ client: _1.Redis.client });
+ }
+ return session(Object.assign({ store: sessionStore, secret: _1.DefaultConfig.SESSION_SECRET || 'keyboard cat', resave: false, saveUninitialized: true, cookie: { secure: false } }, options));
+}
+exports.Session = {
+ getRouter,
+};