webcm
Version:
Demonstrative implementation of a web-based manager for utilising Managed Components
233 lines (232 loc) • 10.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServerFromConfig = void 0;
const express_1 = __importDefault(require("express"));
const http_proxy_middleware_1 = require("http-proxy-middleware");
const path = __importStar(require("path"));
const client_1 = require("./client");
const manager_1 = require("./manager");
const config_1 = require("./config");
const constants_1 = require("./constants");
const static_server_1 = require("./static-server");
const locreq_1 = __importDefault(require("locreq"));
const locreq = (0, locreq_1.default)(__dirname);
const DEFAULT_TARGET = 'http://localhost:8000';
if (process.env.NODE_ENV === 'production') {
process.on('unhandledRejection', (reason) => {
console.log('Unhandled Rejection at:', reason.stack || reason);
});
}
async function startServerFromConfig({ configPath, componentsFolderPath, url, ...args }) {
const config = (0, config_1.getConfig)(configPath);
let componentsPath = '';
if (componentsFolderPath) {
componentsPath = path.resolve(componentsFolderPath);
}
else {
console.log('Components folder path not provided');
}
const { hostname, port, trackPath, components } = config;
if ('customComponentPath' in args && args.customComponentPath) {
console.log(`⚠️ Custom component ${args.customComponentPath} will run with all permissions enabled, use webcm.config.ts to change what permissions it gets`);
components.push({
path: path.resolve(args.customComponentPath),
permissions: Object.values(constants_1.PERMISSIONS),
settings: args.customComponentSettings || {},
});
}
if (url) {
if (!(url.startsWith('http://') || url.startsWith('https://'))) {
url = 'http://' + url;
}
config.target = url;
}
else if (!config.target) {
const server = new static_server_1.StaticServer(8000);
server.start();
console.log('Started a demo static server at localhost:8000');
config.target = DEFAULT_TARGET;
}
const manager = new manager_1.ManagerGeneric({
components,
trackPath,
componentsFolderPath: componentsPath,
});
await manager.init();
const getDefaultPayload = () => ({
pageVars: [],
fetch: [],
execute: [],
return: undefined,
});
const handleClientCreated = (req, _, clientGeneric) => {
const cookieName = 'webcm_clientcreated';
const eventName = 'clientcreated';
let clientAlreadyCreated = clientGeneric.cookies.get(cookieName) || '';
if (!manager.listeners[eventName])
return;
for (const componentName of Object.keys(manager.listeners[eventName])) {
if (clientAlreadyCreated.split(',')?.includes(componentName))
continue;
const event = new manager_1.MCEvent(eventName, req);
event.client = new client_1.Client(componentName, clientGeneric);
clientAlreadyCreated = Array.from(new Set([...clientAlreadyCreated.split(','), componentName])).join(',');
clientGeneric.set(cookieName, clientAlreadyCreated);
manager.listeners[eventName][componentName]?.forEach((fn) => fn(event));
}
};
const handleEvent = async (eventType, req, res) => {
res.payload = getDefaultPayload();
const clientGeneric = new client_1.ClientGeneric(req, res, manager, config);
handleClientCreated(req, res, clientGeneric);
if (manager.listeners[eventType]) {
// slightly alter ecommerce payload
if (eventType === 'ecommerce') {
req.body.payload.ecommerce = { ...req.body.payload.data };
delete req.body.payload.data;
}
const event = new manager_1.MCEvent(eventType, req);
for (const componentName of Object.keys(manager.listeners[eventType])) {
event.client = new client_1.Client(componentName, clientGeneric);
await Promise.all(manager.listeners[eventType][componentName].map((fn) => fn(event)));
}
res.payload.execute.push(manager.getInjectedScript(clientGeneric));
}
return res.end(JSON.stringify(res.payload));
};
const handleClientEvent = async (req, res) => {
res.payload = getDefaultPayload();
const event = new manager_1.MCEvent(req.body.payload.event, req);
const clientGeneric = new client_1.ClientGeneric(req, res, manager, config);
const clientComponentNames = Object.entries(clientGeneric.webcmPrefs.listeners)
.filter(([, events]) => events.includes(req.body.payload.event))
.map(([componentName]) => componentName);
for (const component of clientComponentNames) {
event.client = new client_1.Client(component, clientGeneric);
try {
await manager.clientListeners[req.body.payload.event + '__' + component](event);
}
catch {
console.error(`Error dispatching ${req.body.payload.event} to ${component}: it isn't registered`);
}
}
res.payload.execute.push(manager.getInjectedScript(clientGeneric));
res.end(JSON.stringify(res.payload));
};
// 'event', 'ecommerce' 'pageview', 'client' are the standard types
// 'remarketing', 'identify' or any other event type
const handleTrack = (req, res) => {
const eventType = req.body.eventType;
if (eventType === 'client') {
return handleClientEvent(req, res);
}
else {
return handleEvent(eventType, req, res);
}
};
const handleRequest = (req, clientGeneric) => {
if (!manager.listeners['request'])
return;
const requestEvent = new manager_1.MCEvent('request', req);
for (const componentName of Object.keys(manager.listeners['request'])) {
requestEvent.client = new client_1.Client(componentName, clientGeneric);
manager.listeners['request'][componentName]?.forEach((fn) => fn(requestEvent));
}
};
const app = (0, express_1.default)().use(express_1.default.json());
app.set('trust proxy', true);
// Mount WebCM endpoint
app.post(trackPath, handleTrack);
// Mount components endpoints
for (const route of Object.keys(manager.mappedEndpoints)) {
app.all(route, async (req, res) => {
const response = await manager.mappedEndpoints[route](req);
for (const [headerName, headerValue] of response.headers.entries()) {
res.set(headerName, headerValue);
}
res.status(response.status);
let isDone = false;
const reader = response.body?.getReader();
while (!isDone && reader) {
const { value, done } = await reader.read();
if (value)
res.write(Buffer.from(value));
isDone = done;
}
res.end();
});
}
// Mount components proxied endpoints
for (const component of Object.keys(manager.proxiedEndpoints)) {
for (const [path, proxyTarget] of Object.entries(manager.proxiedEndpoints[component])) {
const proxyEndpoint = '/webcm/' + component + path;
app.all(proxyEndpoint + '*', async (req, res, next) => {
const proxy = (0, http_proxy_middleware_1.createProxyMiddleware)({
target: proxyTarget + req.path.replace(proxyEndpoint, ''),
ignorePath: true,
followRedirects: true,
changeOrigin: true,
});
proxy(req, res, next);
});
}
}
// Mount static files
for (const [filePath, fileTarget] of Object.entries(manager.staticFiles)) {
app.use(filePath, express_1.default.static(path.join(componentsPath, fileTarget)));
}
// Listen to all normal requests
app.use('**', (req, res, next) => {
res.payload = getDefaultPayload();
const clientGeneric = new client_1.ClientGeneric(req, res, manager, config);
const proxySettings = {
target: config.target,
changeOrigin: true,
selfHandleResponse: true,
onProxyReq: (_proxyReq, req, _res) => {
handleRequest(req, clientGeneric);
},
onProxyRes: (0, http_proxy_middleware_1.responseInterceptor)(async (responseBuffer, _proxyRes, proxyReq, _res) => {
if (proxyReq.headers['accept']?.toLowerCase().includes('text/html')) {
let response = responseBuffer.toString('utf8');
response = await manager.processEmbeds(response);
response = await manager.processWidgets(response);
return response.replace('<head>', `<head><script>${manager.getInjectedScript(clientGeneric)};webcm._processServerResponse(${JSON.stringify(res.payload)})</script>`);
}
return responseBuffer;
}),
};
const proxy = (0, http_proxy_middleware_1.createProxyMiddleware)(proxySettings);
proxy(req, res, next);
});
console.info('\nWebCM, version', process.env.npm_package_version || locreq('package.json').version);
app.listen(port, hostname);
console.info(`\n🚀 WebCM is now proxying ${config.target} at http://${hostname}:${port}\n\n`);
}
exports.startServerFromConfig = startServerFromConfig;