novu
Version:
Novu CLI. Run Novu Studio and sync workflows with Novu Cloud
144 lines (140 loc) • 5.51 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TUNNEL_URL = void 0;
exports.devCommand = devCommand;
const ws_1 = __importDefault(require("ws"));
const ora_1 = __importDefault(require("ora"));
const open_1 = __importDefault(require("open"));
const chalk_1 = __importDefault(require("chalk"));
const ntfr_client_1 = require("@novu/ntfr-client");
const dev_server_1 = require("../../dev-server");
const shared_1 = require("../shared");
const index_1 = require("../../index");
const utils_1 = require("./utils");
const package_json_1 = __importDefault(require("../../../package.json"));
process.on('SIGINT', function () {
process.exit();
});
let tunnelClient = null;
exports.TUNNEL_URL = 'https://novu.sh/api/tunnels';
const { version } = package_json_1.default;
async function devCommand(options, anonymousId) {
await (0, shared_1.showWelcomeScreen)();
const parsedOptions = (0, utils_1.parseOptions)(options);
const NOVU_ENDPOINT_PATH = options.route;
let tunnelOrigin;
const devSpinner = (0, ora_1.default)('Creating a development local tunnel').start();
if (parsedOptions.tunnel) {
tunnelOrigin = parsedOptions.tunnel;
}
else {
tunnelOrigin = await createTunnel(parsedOptions.origin, NOVU_ENDPOINT_PATH);
}
devSpinner.succeed(`🛣️ Tunnel → ${tunnelOrigin}${NOVU_ENDPOINT_PATH}`);
const opts = Object.assign(Object.assign({}, parsedOptions), { tunnelOrigin,
anonymousId });
const httpServer = new dev_server_1.DevServer(opts);
const dashboardSpinner = (0, ora_1.default)('Opening dashboard').start();
const studioSpinner = (0, ora_1.default)('Starting local studio server').start();
await httpServer.listen();
dashboardSpinner.succeed(`🖥️ Dashboard → ${parsedOptions.dashboardUrl}`);
studioSpinner.succeed(`🎨 Studio → ${httpServer.getStudioAddress()}`);
if (process.env.NODE_ENV !== 'dev' && parsedOptions.headless === false) {
await (0, open_1.default)(httpServer.getStudioAddress());
}
await monitorEndpointHealth(parsedOptions, NOVU_ENDPOINT_PATH);
}
async function monitorEndpointHealth(parsedOptions, endpointRoute) {
const fullEndpoint = `${parsedOptions.origin}${endpointRoute}`;
let healthy = false;
const endpointText = `Bridge Endpoint scan:\t${fullEndpoint}
Ensure your application is configured and running locally.`;
const endpointSpinner = (0, ora_1.default)(endpointText).start();
let counter = 0;
while (!healthy) {
try {
healthy = await tunnelHealthCheck(fullEndpoint);
if (healthy) {
endpointSpinner.succeed(`🌉 Endpoint → ${fullEndpoint}`);
}
else {
await (0, utils_1.wait)(1000);
}
}
catch (e) {
await (0, utils_1.wait)(1000);
}
finally {
counter += 1;
if (counter === 10) {
endpointSpinner.text = `Bridge Endpoint scan:\t${fullEndpoint}
Ensure your application is configured and running locally.
Starting out? Use our starter ${chalk_1.default.bold('npx novu@latest init')}
Running on a different route or port? Use ${chalk_1.default.bold('--route')} or ${chalk_1.default.bold('--port')}
`;
}
}
}
}
async function tunnelHealthCheck(configTunnelUrl) {
try {
const res = await (await fetch(`${configTunnelUrl}?action=health-check`, {
method: 'GET',
headers: {
accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': `novu@${version}`,
},
})).json();
return res.status === 'ok';
}
catch (e) {
return false;
}
}
async function createTunnel(localOrigin, endpointRoute) {
const originUrl = new URL(localOrigin);
const configTunnelUrl = index_1.config.getValue(`tunnelUrl-${parseInt(originUrl.port, 10)}`);
const storeUrl = configTunnelUrl ? new URL(configTunnelUrl) : null;
if (storeUrl) {
try {
await connectToTunnel(storeUrl, originUrl);
if (tunnelClient.isConnected) {
return storeUrl.origin;
}
}
catch (error) {
return await connectToNewTunnel(originUrl);
}
}
return await connectToNewTunnel(originUrl);
}
async function fetchNewTunnel(originUrl) {
const response = await fetch(exports.TUNNEL_URL, {
method: 'POST',
headers: {
accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer 12345`,
},
});
const { url } = (await response.json());
index_1.config.setValue(`tunnelUrl-${parseInt(originUrl.port, 10)}`, url);
return new URL(url);
}
async function connectToTunnel(parsedUrl, parsedOrigin) {
tunnelClient = new ntfr_client_1.NtfrTunnel(parsedUrl.host, parsedOrigin.host, false, {
WebSocket: ws_1.default,
connectionTimeout: 2000,
maxRetries: Infinity,
}, { verbose: false });
await tunnelClient.connect();
}
async function connectToNewTunnel(originUrl) {
const parsedUrl = await fetchNewTunnel(originUrl);
await connectToTunnel(parsedUrl, originUrl);
return parsedUrl.origin;
}