novu
Version:
Novu CLI. Run Novu Studio and sync workflows with Novu Cloud
138 lines (124 loc) • 5.21 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DevServer = exports.STUDIO_PATH = exports.WELL_KNOWN_ROUTE = void 0;
const node_http_1 = __importDefault(require("node:http"));
const get_port_1 = __importDefault(require("get-port"));
exports.WELL_KNOWN_ROUTE = '/.well-known/novu';
exports.STUDIO_PATH = '/studio';
class DevServer {
constructor(options) {
this.options = options;
}
async listen() {
const port = await (0, get_port_1.default)({ host: this.options.studioHost, port: Number(this.options.studioPort) });
this.server = node_http_1.default.createServer();
this.server.on('request', async (req, res) => {
try {
if (req.url.startsWith(exports.WELL_KNOWN_ROUTE)) {
this.serveWellKnownPath(req, res);
}
else if (req.url.startsWith(exports.STUDIO_PATH)) {
this.serveStudio(req, res);
}
else {
res
.writeHead(301, {
Location: exports.STUDIO_PATH,
})
.end();
}
}
catch (e) {
console.error(e);
}
});
await new Promise((resolve) => {
this.server.listen(port, this.options.studioHost, () => {
resolve();
});
});
}
getAddress() {
const response = this.server.address();
return `http://${this.options.studioHost}:${response.port}`;
}
getStudioAddress() {
return `${this.getAddress()}${exports.STUDIO_PATH}`;
}
close() {
this.server.close();
}
serveWellKnownPath(req, res) {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', this.options.dashboardUrl);
res.end(JSON.stringify(this.options));
}
serveStudio(req, res) {
const studioHTML = `
<html class="dark">
<head>
<link href="${this.options.dashboardUrl}/favicon.svg" rel="icon" />
<title>Novu Studio</title>
</head>
<body style="padding: 0; margin: 0; overflow: hidden;">
<script>
const NOVU_CLOUD_STUDIO_ORIGIN = '${this.options.dashboardUrl}';
function injectIframe(src) {
/*
* Updates the URL in the parent window for better navigation control.
* Example: If the user enters 'http://localhost:PORT/studio/onboarding/preview', it remains unchanged,
* otherwise, redirects back to 'http://localhost:PORT/studio/onboarding'.
*/
const getWindowsUrl = (url) => {
const studioPath = '/studio';
const pathname = window.location.pathname;
return url.includes(studioPath) ? url.replace(studioPath, pathname) : url;
};
const iframe = window.document.createElement('iframe');
iframe.sandbox = 'allow-forms allow-scripts allow-modals allow-same-origin allow-popups allow-popups-to-escape-sandbox'
iframe.allow = 'clipboard-read; clipboard-write'
iframe.style = 'width: 100%; height: 100vh; border: none;';
const currentUrl = getWindowsUrl(src)
iframe.setAttribute('src', currentUrl);
document.body.appendChild(iframe);
window.addEventListener('message', (event) => {
if (event?.data?.type === 'pathnameChange') {
history.replaceState(null, '', event.data?.pathname);
}
});
return iframe;
}
function redirectToCloud() {
// Replace the local tunnel with Novu Web Dashboard on build time.
const url = new URL('/local-studio/auth', NOVU_CLOUD_STUDIO_ORIGIN);
url.searchParams.set('redirect_url', window.location.href);
url.searchParams.set('application_origin', '${this.options.origin}');
url.searchParams.set('tunnel_origin', '${this.options.tunnelOrigin}');
url.searchParams.set('tunnel_route', '${this.options.route}');
url.searchParams.set('anonymous_id', '${this.options.anonymousId}');
window.location.href = url.href;
}
function bootstrapLocalStudio() {
const url = new URL(window.location.href);
const localStudioURL = url.searchParams.get('local_studio_url');
url.searchParams.delete('local_studio_url');
history.replaceState({}, '', url.href);
if (!localStudioURL) {
return redirectToCloud();
}
injectIframe(localStudioURL);
}
bootstrapLocalStudio();
</script>
</body>
</html>
`;
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write(studioHTML);
res.end();
}
}
exports.DevServer = DevServer;