repo_description:  💬 🤖  The most reliable tool for chatbots with advanced features. Be sure to 🌟 this repository for updates! 
repo_topics: api chatbots framework nodejs typescript
code: import { create, ev, log, now, processSend, processSendData } from '../index'
  import terminalLink from 'terminal-link';
  import isUrl from 'is-url-superb';
  import tcpPortUsed from 'tcp-port-used';
  import { default as axios } from 'axios'
  import { cli } from './setup';
  import { collections, generateCollections } from './collections';
  import { setUpExpressApp, setupTunnel, setupMetaProcessMiddleware, setupAuthenticationLayer, setupRefocusDisengageMiddleware, setupApiDocs, setupSwaggerStatsMiddleware, setupMediaMiddleware, app, setupSocketServer, server, setupBotPressHandler, setupTwilioCompatibleWebhook, enableCORSRequests, setupChatwoot, setupHttpServer } from './server';
  import { setupChatwootOutgoingMessageHandler } from './integrations/chatwoot';
  let checkUrl = (s : any) => (typeof s === "string") && isUrl(s);
  
  const ready: (config : any) => Promise<void> = async (config : any) => {
      processSend('ready');
      if(config.readyWebhook)
      await axios({
          method: 'post',
          url: config.readyWebhook,
          data: {
              ts: Date.now(),
              data: {
                  ...config
              },
              sessionId: config.sessionId,
              namespace: "READY"
          }
      }).catch(err => log.error(`READY WEBHOOK ERROR: ${config.readyWebhook} ${err.message}`));
  }
  
  async function start() {
  
      const { cliConfig, createConfig, PORT, spinner } = await cli()
      process.env.OWA_CLI = "true"
      spinner.start("Launching EASY API")
      if(cliConfig.verbose) log.info(`config: `,{cliConfig, createConfig, PORT})
      setUpExpressApp();
      if(cliConfig.cors) await enableCORSRequests(cliConfig);
      try {
          const { status, data } = await axios.post(`http://localhost:${PORT}/getConnectionState`);
          if (status === 200 && data.response === "CONNECTED") {
              const { data: { response: { sessionId, port, webhook, apiHost } } } = await axios.post(`http://localhost:${PORT}/getConfig`);
              if (createConfig?.sessionId == sessionId && createConfig.port === port && createConfig.webhook === webhook && createConfig.apiHost === apiHost) {
                  spinner.info('removing popup flag')
                  if (createConfig.popup) {
                      delete createConfig.popup;
                  }
              }
          }
      } catch (error) {
          if (error.code === "ECONNREFUSED") spinner.info('Selected port is free')
      }
      createConfig.headless = cliConfig.headful != undefined ? !cliConfig.headful : cliConfig.headless
      if (cliConfig.ev || cliConfig.ev == "") {
          //prepare ef
          if (cliConfig?.ef) {
              if (!Array.isArray(cliConfig.ef)) cliConfig.ef = [cliConfig.ef]
              cliConfig.ef = cliConfig.ef.flatMap(s => s.split(','))
          }
          if(cliConfig.skipUrlCheck) checkUrl = () => true
          if (!checkUrl(cliConfig.ev)) spinner.fail("--ev/-e expecting URL - invalid URL.")
          else ev.on('**', async (data, sessionId, namespace) => {
              if (cliConfig?.ef) {
                  if (!cliConfig.ef.includes(namespace)) return;
              }
              if (!cliConfig.allowSessionDataWebhook && (namespace == "sessionData" || namespace == "sessionDataBase64")) return;
              const whStart = now();
              await axios({
                  method: 'post',
                  url: cliConfig.ev,
                  data: {
                      ts: Date.now(),
                      data,
                      sessionId,
                      namespace
                  }
              }).then(({status})=>{
                  const t = (now() - whStart).toFixed(0);
                  log.info("EV Webhook", namespace, status, t)
              }).catch(err => log.error(`EV WEBHOOK ERROR: ${cliConfig.ev} ${err.message}`));
          })
      }
      //These things can be done before the client is created
      if (cliConfig?.generateApiDocs || cliConfig?.stats) {
          await generateCollections({
              ...createConfig,
              ...cliConfig
          }, spinner)
      }
  
      try {
          const client = await create({ ...createConfig });
          setupMetaProcessMiddleware(client, cliConfig)
          await setupHttpServer(cliConfig)
          if(cliConfig.autoReject){
              await client.autoReject(cliConfig.onCall)
          } else if(cliConfig.onCall) {
              await client.onIncomingCall(async call => {
                  await client.sendText(call.peerJid, cliConfig.onCall)
              })
          }
          client.onLogout(async () => {
              console.error('!!!! CLIENT LOGGED OUT !!!!')
              if (cliConfig && !cliConfig.noKillOnLogout) {
                  await client.waitAllQEmpty();
                  console.error("Shutting down.")
                  process.exit();
              }
          }, -1)
  
          if(cliConfig?.botPressUrl){
              spinner.info('Setting Up Botpress handler');
              setupBotPressHandler(cliConfig, client)
              spinner.succeed('Botpress handler set up successfully');
          }
  
          if(cliConfig?.twilioWebhook){
              spinner.info('Setting Up Twilio Compaitible Webhook');
              setupTwilioCompatibleWebhook(cliConfig, client)
              spinner.succeed('Twilio Compaitible Webhook set up successfully');
          }
  
          
          if (cliConfig?.webhook) {
              if (Array.isArray(cliConfig.webhook)) {
                  await Promise.all(cliConfig.webhook.map(webhook => {
                      if (webhook.url && webhook.events) return client.registerWebhook(webhook.url, webhook.events, webhook.requestConfig || {})
                  }))
              } else await client.registerWebhook(cliConfig.webhook, "all")
          }
          if (cliConfig?.keepAlive) client.onStateChanged(async state => {
              if ((state === "CONFLICT" || state === "UNLAUNCHED") && cliConfig?.keepAlive) await client.forceRefocus();
          });
  
          if (!cliConfig?.noApi) {
              if (cliConfig?.key) {
                  spinner.info(`Please see machine logs to see the API key`)
                  console.log(`Please use the following api key for requests as a header:\napi_key: ${cliConfig.key}`)
                  setupAuthenticationLayer(cliConfig)
              }
  
              if(cliConfig?.chatwootUrl){
                  spinner.info('Setting Up Chatwoot handler');
                  spinner.info(`Make sure to set up the Chatwoot inbox webhook to the following path on this process: /chatwoot${cliConfig.key ? `?api_key=YOUR-API-KEY` : ''}`);
                  await setupChatwoot(cliConfig, client);
                  spinner.succeed('Chatwoot handler set up successfully');
              }
  
              setupRefocusDisengageMiddleware(cliConfig)
  
              if (cliConfig && cliConfig.generateApiDocs && collections["swagger"]) {
                  spinner.info('Setting Up API Explorer');
                  setupApiDocs(cliConfig)
                  spinner.succeed('API Explorer set up successfully');
              }
  
              if (cliConfig?.stats && collections["swagger"]) {
                  spinner.info('Setting Up API Stats');
                  setupSwaggerStatsMiddleware(cliConfig)
                  spinner.info('API Stats set up successfully');
              }
              if (createConfig.messagePreprocessor === "AUTO_DECRYPT_SAVE") {
                  setupMediaMiddleware();
              }
              app.use(client.middleware((cliConfig && cliConfig.useSessionIdInPath), PORT));
  
              if (cliConfig.socket) {
                  spinner.info("Setting up socket")
                  await setupSocketServer(cliConfig, client)
                  spinner.succeed("Socket ready for connection")
              }
              spinner.info(`...waiting for port ${PORT} to be free`);
              await tcpPortUsed.waitUntilFree(PORT, 200, 20000).catch(()=>{
                  spinner.fail(`Port ${PORT} is not available. Closing`);
                  process.exit();
              })
              spinner.succeed(`Port ${PORT} is now free.`);
              server.listen(PORT, async () => {
                  spinner.succeed(`\n• Listening on port ${PORT}!`);
                  processSendData({port:PORT})
              });
              process.on('message', async function (data : any) {
                  if(data?.data?.command === "port_report") {
                      const response = {port:PORT};
                      processSendData(response);
                      return response
                  }
              });
              if(cliConfig.tunnel) {
                  spinner.info(`\n• Setting up external tunnel`);
                  const tunnelUrl = await setupTunnel(cliConfig, PORT)
                  spinner.succeed(`\n\t${terminalLink('External address', tunnelUrl)}`)
  
              } 
              const apiDocsUrl = cliConfig.apiHost ? `${cliConfig.apiHost}/api-docs/ ` : `${cliConfig.host.includes('http') ? '' : 'http://'}${cliConfig.host}:${PORT}/api-docs/ `;
              const link = terminalLink('API Explorer', apiDocsUrl);
              if (cliConfig && cliConfig.generateApiDocs) spinner.succeed(`\n\t${link}`)
  
              if (cliConfig && cliConfig.generateApiDocs && cliConfig.stats) {
                  const swaggerStatsUrl = cliConfig.apiHost ? `${cliConfig.apiHost}/swagger-stats/ ` : `${cliConfig.host.includes('http') ? '' : 'http://'}${cliConfig.host}:${PORT}/swagger-stats/ `;
                  const statsLink = terminalLink('API Stats', swaggerStatsUrl);
                  spinner.succeed(`\n\t${statsLink}`)
              }
              if(cliConfig?.chatwootUrl) await setupChatwootOutgoingMessageHandler(cliConfig, client);
          }
          await ready({...createConfig, ...cliConfig, ...client.getSessionInfo(), hostAccountNumber: await client.getHostNumber()});
          if (cliConfig.emitUnread) {
              await client.emitUnreadMessages()
          }
          if(!createConfig.licenseKey) spinner.succeed(`Use this link to get a license: ${await client.getLicenseLink()}`);
      } catch (e) {
          spinner.fail(`Error ${e.message} ${e}`)
      }
  }
  
  start();