/*****************************************************************************\
|                                               ( )_  _                       |
|    _ _    _ __   _ _    __    ___ ___     _ _ | ,_)(_)  ___   ___     _     |
|   ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| |  | |/',__)/' v `\ /'_`\   |
|   | (_) )| |  ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) )  |
|   | ,__/'(_)  `\__,_)`\__  |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/'  |
|   | |                ( )_) |                                                |
|   (_)                 \___/'                                                |
|                                                                             |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved.             |
| Licensed under the AGPL-3.0.                                                |
|                                                                             |
| According to our dual licensing model, this program can be used either      |
| under the terms of the GNU Affero General Public License, version 3,        |
| or under a proprietary license.                                             |
|                                                                             |
| The texts of the GNU Affero General Public License with an additional       |
| permission and of our proprietary license can be found at and               |
| in the LICENSE file you have received along with this program.              |
|                                                                             |
| This program is distributed in the hope that it will be useful,             |
| but WITHOUT ANY WARRANTY, without even the implied warranty of              |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                |
| GNU Affero General Public License for more details.                         |
|                                                                             |
| "General Bots" is a registered trademark of Pragmatismo.io.                 |
| The licensing of the program under the AGPLv3 does not imply a              |
| trademark license. Therefore any rights, title and interest in              |
| our trademarks remain entirely with us.                                     |
|                                                                             |
\*****************************************************************************/
/**
 * @fileoverview General Bots server core.
 */
'use strict';
import express from 'express';
import bodyParser from 'body-parser';
import https from 'https';
import http from 'http';
import mkdirp from 'mkdirp';
import Path from 'path';
import * as Fs from 'fs';
import { GBLog } from 'botlib';
import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService.js';
import { AzureDeployerService } from '../packages/azuredeployer.gbapp/services/AzureDeployerService.js';
import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService.js';
import { GBConversationalService } from '../packages/core.gbapp/services/GBConversationalService.js';
import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService.js';
import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer.js';
import { GBImporter } from '../packages/core.gbapp/services/GBImporterService.js';
import { GBMinService } from '../packages/core.gbapp/services/GBMinService.js';
import auth from 'basic-auth';
import { ChatGPTAPIBrowser } from 'chatgpt';
import child_process from 'child_process';
import * as winston from 'winston-logs-display';
import { RootData } from './RootData.js';
import { GBSSR } from '../packages/core.gbapp/services/GBSSR.js';
import { Mutex } from 'async-mutex';
/**
 * General Bots open-core entry point.
 */
export class GBServer {
    static globals;
    /**
     *  Program entry-point.
     */
    static run() {
        GBLog.info(`The Bot Server is in STARTING mode...`);
        GBServer.globals = new RootData();
        GBConfigService.init();
        const port = GBConfigService.getServerPort();
        if (process.env.TEST_SHELL) {
            GBLog.info(`Running TEST_SHELL: ${process.env.TEST_SHELL}...`);
            try {
                child_process.execSync(process.env.TEST_SHELL);
            }
            catch (error) {
                GBLog.error(`Running TEST_SHELL ERROR: ${error}...`);
            }
        }
        const server = express();
        GBServer.globals.server = server;
        GBServer.globals.httpsServer = null;
        GBServer.globals.webSessions = {};
        GBServer.globals.processes = {};
        GBServer.globals.files = {};
        GBServer.globals.appPackages = [];
        GBServer.globals.sysPackages = [];
        GBServer.globals.minInstances = [];
        GBServer.globals.wwwroot = null;
        GBServer.globals.entryPointDialog = null;
        GBServer.globals.debuggers = [];
        GBServer.globals.indexSemaphore = new Mutex();
        server.use(bodyParser.json());
        server.use(bodyParser.urlencoded({ extended: true }));
        // Setups global error handlers.
        process.on('unhandledRejection', (err, p) => {
            GBLog.error(`UNHANDLED_REJECTION(promises): ${err.toString()} ${err['stack'] ? '\n' + err['stack'] : ''}`);
            if (err['response']?.obj?.httpStatusCode === 404) {
                GBLog.warn(`Check reverse proxy: ${process.env.BOT_URL} as it seems to be invalid.`);
            }
        });
        process.on('uncaughtException', (err, p) => {
            if (err !== null) {
                GBLog.error(`UNCAUGHT_EXCEPTION:  ${err.toString()} ${err['stack'] ? '\n' + err['stack'] : ''}`);
            }
            else {
                GBLog.error('UNCAUGHT_EXCEPTION: Unknown error (err is null)');
            }
        });
        process.on('SIGTERM', () => {
            GBLog.info('SIGTERM signal received.');
        });
        // Creates working directory.
        process.env.PWD = process.cwd();
        const workDir = Path.join(process.env.PWD, 'work');
        if (!Fs.existsSync(workDir)) {
            mkdirp.sync(workDir);
        }
        const mainCallback = () => {
            (async () => {
                try {
                    GBLog.info(`Now accepting connections on ${port}...`);
                    process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
                    // Reads basic configuration, initialize minimal services.
                    const core = new GBCoreService();
                    const importer = new GBImporter(core);
                    const deployer = new GBDeployer(core, importer);
                    const subscriptionId = GBConfigService.get('CLOUD_SUBSCRIPTIONID');
                    let azureDeployer;
                    // Ensure that local proxy is setup.
                    if (process.env.NODE_ENV === 'development') {
                        const proxy = GBConfigService.get('BOT_URL');
                        if (proxy !== undefined) {
                            GBServer.globals.publicAddress = proxy;
                        }
                        else {
                            GBLog.info(`Establishing a development local proxy (proxy) on BOT_URL...`);
                            GBServer.globals.publicAddress = await core.ensureProxy(port);
                        }
                    }
                    else {
                        const serverAddress = process.env.BOT_URL;
                        GBLog.info(`Defining server address at ${serverAddress}...`);
                        GBServer.globals.publicAddress = serverAddress;
                    }
                    // Creates a boot instance or load it from storage.
                    if (GBConfigService.get('STORAGE_SERVER')) {
                        azureDeployer = await AzureDeployerService.createInstance(deployer);
                        await core.initStorage();
                    }
                    else {
                        [GBServer.globals.bootInstance, azureDeployer] = await core['createBootInstanceEx'](core, null, GBServer.globals.publicAddress, deployer, GBConfigService.get('FREE_TIER'));
                    }
                    core.ensureAdminIsSecured();
                    // Deploys system and user packages.
                    GBLog.info(`Deploying packages...`);
                    GBServer.globals.sysPackages = await core.loadSysPackages(core);
                    GBLog.info(`Connecting to Bot Storage...`);
                    await core.checkStorage(azureDeployer);
                    await deployer.deployPackages(core, server, GBServer.globals.appPackages);
                    await core.syncDatabaseStructure();
                    // Deployment of local applications for the first time.
                    if (GBConfigService.get('DISABLE_WEB') !== 'true') {
                        deployer.setupDefaultGBUI();
                    }
                    GBLog.info(`Publishing instances...`);
                    const instances = await core.loadAllInstances(core, azureDeployer, GBServer.globals.publicAddress);
                    if (instances.length === 0) {
                        const instance = await importer.importIfNotExistsBotPackage(GBConfigService.get('BOT_ID'), 'boot.gbot', 'packages/boot.gbot', GBServer.globals.bootInstance);
                        await deployer.deployBotFull(instance, GBServer.globals.publicAddress);
                        instances.push(instance);
                        // Runs the search even with empty content to create structure.
                        await azureDeployer['runSearch'](instance);
                    }
                    GBServer.globals.bootInstance = instances[0];
                    // Builds minimal service infrastructure.
                    const conversationalService = new GBConversationalService(core);
                    const adminService = new GBAdminService(core);
                    const minService = new GBMinService(core, conversationalService, adminService, deployer);
                    GBServer.globals.minService = minService;
                    await minService.buildMin(instances);
                    if (process.env.OPENAI_EMAIL) {
                        if (!GBServer.globals.chatGPT) {
                            GBServer.globals.chatGPT = new ChatGPTAPIBrowser({
                                email: process.env.OPENAI_EMAIL,
                                password: process.env.OPENAI_PASSWORD,
                                markdown: false
                            });
                            await GBServer.globals.chatGPT.init();
                        }
                    }
                    if (process.env.ENABLE_WEBLOG) {
                        // If global log enabled, reorders transports adding web logging.
                        const loggers = GBLog.getLogger();
                        winston.default(server, loggers[1]);
                    }
                    server.get('*', async (req, res, next) => {
                        if (req.originalUrl.startsWith('/logs')) {
                            if (process.env.ENABLE_WEBLOG) {
                                const admins = {
                                    admin: { password: process.env.ADMIN_PASS }
                                };
                                // ... some not authenticated middlewares.
                                const user = auth(req);
                                if (!user || !admins[user.name] || admins[user.name].password !== user.pass) {
                                    res.set('WWW-Authenticate', 'Basic realm="example"');
                                    return res.status(401).send();
                                }
                            }
                            else {
                                await GBSSR.ssrFilter(req, res, next);
                            }
                        }
                        else {
                            await GBSSR.ssrFilter(req, res, next);
                        }
                    });
                    GBLog.info(`The Bot Server is in RUNNING mode...`);
                    // Opens Navigator.
                    if (process.env.DEV_OPEN_BROWSER) {
                        core.openBrowserInDevelopment();
                    }
                }
                catch (err) {
                    GBLog.error(`STOP: ${err.message ? err.message : err} ${err.stack ? err.stack : ''}`);
                    process.exit(1);
                }
            })();
        };
        // Setups unsecure http redirect.
        if (process.env.NODE_ENV === 'production') {
            const server1 = http.createServer((req, res) => {
                const host = req.headers.host.startsWith('www.') ?
                    req.headers.host.substring(4) : req.headers.host;
                res.writeHead(301, {
                    Location: "https://" + host + req.url
                }).end();
            });
            server1.listen(80);
        }
        if (process.env.CERTIFICATE_PFX) {
            const options1 = {
                passphrase: process.env.CERTIFICATE_PASSPHRASE,
                pfx: Fs.readFileSync(process.env.CERTIFICATE_PFX)
            };
            const httpsServer = https.createServer(options1, server).listen(port, mainCallback);
            GBServer.globals.httpsServer = httpsServer;
            for (let i = 2;; i++) {
                const certPfxEnv = `CERTIFICATE${i}_PFX`;
                const certPassphraseEnv = `CERTIFICATE${i}_PASSPHRASE`;
                const certDomainEnv = `CERTIFICATE${i}_DOMAIN`;
                if (process.env[certPfxEnv] && process.env[certPassphraseEnv] && process.env[certDomainEnv]) {
                    const options = {
                        passphrase: process.env[certPassphraseEnv],
                        pfx: Fs.readFileSync(process.env[certPfxEnv])
                    };
                    httpsServer.addContext(process.env[certDomainEnv], options);
                }
                else {
                    break;
                }
            }
        }
        else {
            server.listen(port, mainCallback);
        }
    }
}
//# sourceMappingURL=app.js.map