Last active
April 3, 2025 18:35
-
-
Save mikaelvesavuori/f838c02e4da491ce53368b52264330d2 to your computer and use it in GitHub Desktop.
MikroChat bundled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // MikroChat - See LICENSE file for copyright and license details. | |
| import A from"node:crypto";import{URL as Ye}from"node:url";var J=class extends Error{constructor(e){super(e),this.name="ValidationError",this.message=e||"Validation did not pass",this.cause={statusCode:400}}};import{existsSync as We,readFileSync as _e}from"node:fs";var k=class{config={};options=[];validators=[];autoValidate=!0;constructor(e){let t=e?.configFilePath,r=e?.args||[],s=e?.config||{};this.options=e?.options||[],this.validators=e?.validators||[],e?.autoValidate!==void 0&&(this.autoValidate=e.autoValidate),this.config=this.createConfig(t,r,s)}deepMerge(e,t){let r={...e};for(let s in t)t[s]!==void 0&&(t[s]!==null&&typeof t[s]=="object"&&!Array.isArray(t[s])&&s in e&&e[s]!==null&&typeof e[s]=="object"&&!Array.isArray(e[s])?r[s]=this.deepMerge(e[s],t[s]):t[s]!==void 0&&(r[s]=t[s]));return r}setValueAtPath(e,t,r){let s=t.split("."),i=e;for(let n=0;n<s.length-1;n++){let l=s[n];!(l in i)||i[l]===null?i[l]={}:typeof i[l]!="object"&&(i[l]={}),i=i[l]}let o=s[s.length-1];i[o]=r}getValueAtPath(e,t){let r=t.split("."),s=e;for(let i of r){if(s==null)return;s=s[i]}return s}createConfig(e,t=[],r={}){let s={};for(let l of this.options)l.defaultValue!==void 0&&this.setValueAtPath(s,l.path,l.defaultValue);let i={};if(e&&We(e))try{let l=_e(e,"utf8");i=JSON.parse(l),console.log(`Loaded configuration from ${e}`)}catch(l){console.error(`Error reading config file: ${l instanceof Error?l.message:String(l)}`)}let o=this.parseCliArgs(t),n=this.deepMerge({},s);return n=this.deepMerge(n,i),n=this.deepMerge(n,r),n=this.deepMerge(n,o),n}parseCliArgs(e){let t={},r=e[0]?.endsWith("node")||e[0]?.endsWith("node.exe")?2:0;for(;r<e.length;){let s=e[r++],i=this.options.find(o=>o.flag===s);if(i)if(i.isFlag)this.setValueAtPath(t,i.path,!0);else if(r<e.length&&!e[r].startsWith("-")){let o=e[r++];if(i.parser)try{o=i.parser(o)}catch(n){console.error(`Error parsing value for ${i.flag}: ${n instanceof Error?n.message:String(n)}`);continue}if(i.validator){let n=i.validator(o);if(n!==!0&&typeof n=="string"){console.error(`Invalid value for ${i.flag}: ${n}`);continue}if(n===!1){console.error(`Invalid value for ${i.flag}`);continue}}this.setValueAtPath(t,i.path,o)}else console.error(`Missing value for option ${s}`)}return t}validate(){for(let e of this.validators){let t=this.getValueAtPath(this.config,e.path),r=e.validator(t,this.config);if(r===!1)throw new J(e.message);if(typeof r=="string")throw new J(r)}}get(){return this.autoValidate&&this.validate(),this.config}getValue(e,t){let r=this.getValueAtPath(this.config,e);return r!==void 0?r:t}setValue(e,t){if(typeof t=="object"&&t!==null&&!Array.isArray(t)){let r=this.getValueAtPath(this.config,e)||{};if(typeof r=="object"&&!Array.isArray(r)){let s=this.deepMerge(r,t);this.setValueAtPath(this.config,e,s);return}}this.setValueAtPath(this.config,e,t)}getHelpText(){let e=`Available configuration options: | |
| `;for(let t of this.options)e+=`${t.flag}${t.isFlag?"":" <value>"} | |
| `,t.description&&(e+=` ${t.description} | |
| `),t.defaultValue!==void 0&&(e+=` Default: ${JSON.stringify(t.defaultValue)} | |
| `),e+=` | |
| `;return e}};var T={int:e=>{let t=e.trim();if(!/^[+-]?\d+$/.test(t))throw new Error(`Cannot parse "${e}" as an integer`);let r=Number.parseInt(t,10);if(Number.isNaN(r))throw new Error(`Cannot parse "${e}" as an integer`);return r},float:e=>{let t=e.trim();if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(t)){if(t==="Infinity"||t==="-Infinity")return t==="Infinity"?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;throw new Error(`Cannot parse "${e}" as a number`)}let r=Number.parseFloat(t);if(Number.isNaN(r))throw new Error(`Cannot parse "${e}" as a number`);return r},boolean:e=>{let t=e.trim().toLowerCase();if(["true","yes","1","y"].includes(t))return!0;if(["false","no","0","n"].includes(t))return!1;throw new Error(`Cannot parse "${e}" as a boolean`)},array:e=>e.split(",").map(t=>t.trim()),json:e=>{try{return JSON.parse(e)}catch{throw new Error(`Cannot parse "${e}" as JSON`)}}};import{EventEmitter as Ze}from"node:events";var he=class extends Error{constructor(e){super(),this.name="ValidationError",this.message=e,this.cause={statusCode:400}}};import{existsSync as ze,readFileSync as qe}from"node:fs";var de=class{config;defaults={configFilePath:"mikromail.config.json",args:[]};constructor(e){let t=e?.config||{},r=e?.configFilePath||this.defaults.configFilePath,s=e?.args||this.defaults.args;this.config=this.create(r,s,t)}create(e,t,r){let s={host:"",user:"",password:"",port:465,secure:!0,debug:!1,maxRetries:2},i={};if(ze(e))try{let n=qe(e,"utf8");i=JSON.parse(n),console.log(`Loaded configuration from ${e}`)}catch(n){console.error(`Error reading config file: ${n instanceof Error?n.message:String(n)}`)}let o=this.parseCliArgs(t);return{...s,...r,...i,...o}}parseCliArgs(e){let t={};for(let r=2;r<e.length;r++)switch(e[r]){case"--host":r+1<e.length&&(t.host=e[++r]);break;case"--user":r+1<e.length&&(t.user=e[++r]);break;case"--password":r+1<e.length&&(t.password=e[++r]);break;case"--port":if(r+1<e.length){let i=Number.parseInt(e[++r],10);Number.isNaN(i)||(t.port=i)}break;case"--secure":t.secure=!0;break;case"--debug":t.debug=!0;break;case"--retries":if(r+1<e.length){let i=Number.parseInt(e[++r],10);Number.isNaN(i)||(t.maxRetries=i)}break}return t}validate(){if(!this.config.host)throw new he("Host value not found")}get(){return this.validate(),this.config}};import{promises as Ke}from"node:dns";function N(e){try{let[t,r]=e.split("@");if(!t||t.length>64||t.startsWith(".")||t.endsWith(".")||t.includes("..")||!/^[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~.]+$/.test(t)||!r||r.length>255)return!1;if(r.startsWith("[")&&r.endsWith("]")){let i=r.slice(1,-1);return i.startsWith("IPv6:")?!0:/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/.test(i)}if(r.startsWith(".")||r.endsWith(".")||r.includes(".."))return!1;let s=r.split(".");if(s.length<2||s[s.length-1].length<2)return!1;for(let i of s)if(!i||i.length>63||!/^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?$/.test(i))return!1;return!0}catch{return!1}}async function Ge(e){try{let t=await Ke.resolveMx(e);return!!t&&t.length>0}catch{return!1}}async function fe(e){try{let t=e.split("@")[1];return t?await Ge(t):!1}catch{return!1}}import{Buffer as P}from"node:buffer";import X from"node:crypto";import Je from"node:net";import Xe from"node:os";import me from"node:tls";var ge=class{config;socket;connected;lastCommand;serverCapabilities;secureMode;retryCount;maxEmailSize=10485760;constructor(e){this.config={host:e.host,user:e.user,password:e.password,port:e.port??(e.secure?465:587),secure:e.secure??!0,debug:e.debug??!1,timeout:e.timeout??1e4,clientName:e.clientName??Xe.hostname(),maxRetries:e.maxRetries??3,retryDelay:e.retryDelay??1e3,skipAuthentication:e.skipAuthentication||!1},this.socket=null,this.connected=!1,this.lastCommand="",this.serverCapabilities=[],this.secureMode=this.config.secure,this.retryCount=0}log(e,t=!1){this.config.debug&&console.log(`${t?"SMTP ERROR: ":"SMTP: "}${e}`)}async connect(){return new Promise((e,t)=>{let r=setTimeout(()=>{t(new Error(`Connection timeout after ${this.config.timeout}ms`)),this.socket?.destroy()},this.config.timeout);try{this.config.secure?this.createTLSConnection(r,e,t):this.createPlainConnection(r,e,t)}catch(s){clearTimeout(r),this.log(`Failed to create socket: ${s.message}`,!0),t(s)}})}createTLSConnection(e,t,r){this.socket=me.connect({host:this.config.host,port:this.config.port,rejectUnauthorized:!0,minVersion:"TLSv1.2",ciphers:"HIGH:!aNULL:!MD5:!RC4"}),this.setupSocketEventHandlers(e,t,r)}createPlainConnection(e,t,r){this.socket=Je.createConnection({host:this.config.host,port:this.config.port}),this.setupSocketEventHandlers(e,t,r)}setupSocketEventHandlers(e,t,r){this.socket&&(this.socket.once("error",s=>{clearTimeout(e),this.log(`Connection error: ${s.message}`,!0),r(new Error(`SMTP connection error: ${s.message}`))}),this.socket.once("connect",()=>{this.log("Connected to SMTP server"),clearTimeout(e),this.socket.once("data",s=>{let i=s.toString().trim();this.log(`Server greeting: ${i}`),i.startsWith("220")?(this.connected=!0,this.secureMode=this.config.secure,t()):(r(new Error(`Unexpected server greeting: ${i}`)),this.socket.destroy())})}),this.socket.once("close",s=>{this.connected?this.log(`Connection closed${s?" with error":""}`):(clearTimeout(e),r(new Error("Connection closed before initialization completed"))),this.connected=!1}))}async upgradeToTLS(){if(!(!this.socket||this.secureMode))return new Promise((e,t)=>{let s={socket:this.socket,host:this.config.host,rejectUnauthorized:!0,minVersion:"TLSv1.2",ciphers:"HIGH:!aNULL:!MD5:!RC4"},i=me.connect(s);i.once("error",o=>{this.log(`TLS upgrade error: ${o.message}`,!0),t(new Error(`STARTTLS error: ${o.message}`))}),i.once("secureConnect",()=>{this.log("Connection upgraded to TLS"),i.authorized?(this.socket=i,this.secureMode=!0,e()):t(new Error(`TLS certificate verification failed: ${i.authorizationError}`))})})}async sendCommand(e,t,r=this.config.timeout){if(!this.socket||!this.connected)throw new Error("Not connected to SMTP server");return new Promise((s,i)=>{let o=setTimeout(()=>{this.socket?.removeListener("data",l),i(new Error(`Command timeout after ${r}ms: ${e}`))},r),n="",l=a=>{n+=a.toString();let c=n.split(`\r | |
| `);if(c.length>0&&c[c.length-1]===""){let u=c[c.length-2]||"",h=/^(\d{3})(.?)/.exec(u);h?.[1]&&h[2]!=="-"&&(this.socket?.removeListener("data",l),clearTimeout(o),this.log(`SMTP Response: ${n.trim()}`),h[1]===t.toString()?s(n.trim()):i(new Error(`SMTP Error: ${n.trim()}`)))}};this.socket.on("data",l),e.startsWith("AUTH PLAIN")||e.startsWith("AUTH LOGIN")||this.lastCommand==="AUTH LOGIN"&&!e.startsWith("AUTH")?this.log("SMTP Command: [Credentials hidden]"):this.log(`SMTP Command: ${e}`),this.lastCommand=e,this.socket.write(`${e}\r | |
| `)})}parseCapabilities(e){let t=e.split(`\r | |
| `);this.serverCapabilities=[];for(let r=1;r<t.length;r++){let s=t[r];if(s.match(/^\d{3}/)&&s.charAt(3)===" "){let i=s.substr(4).toUpperCase();this.serverCapabilities.push(i)}}this.log(`Server capabilities: ${this.serverCapabilities.join(", ")}`)}getBestAuthMethod(){if(this.serverCapabilities.map(t=>t.split(" ")[0]).includes("AUTH")){let t=this.serverCapabilities.find(r=>r.startsWith("AUTH "));if(t){let r=t.split(" ").slice(1);if(r.includes("CRAM-MD5"))return"CRAM-MD5";if(r.includes("LOGIN"))return"LOGIN";if(r.includes("PLAIN"))return"PLAIN"}}return"PLAIN"}async authenticate(){switch(this.getBestAuthMethod()){case"CRAM-MD5":await this.authenticateCramMD5();break;case"LOGIN":await this.authenticateLogin();break;default:await this.authenticatePlain();break}}async authenticatePlain(){let e=P.from(`\0${this.config.user}\0${this.config.password}`).toString("base64");await this.sendCommand(`AUTH PLAIN ${e}`,235)}async authenticateLogin(){await this.sendCommand("AUTH LOGIN",334),await this.sendCommand(P.from(this.config.user).toString("base64"),334),await this.sendCommand(P.from(this.config.password).toString("base64"),235)}async authenticateCramMD5(){let e=await this.sendCommand("AUTH CRAM-MD5",334),t=P.from(e.substr(4),"base64").toString("utf8"),r=X.createHmac("md5",this.config.password);r.update(t);let s=r.digest("hex"),i=`${this.config.user} ${s}`,o=P.from(i).toString("base64");await this.sendCommand(o,235)}generateMessageId(){let e=X.randomBytes(16).toString("hex"),t=this.config.user.split("@")[1]||"localhost";return`<${e}@${t}>`}generateBoundary(){return`----=_NextPart_${X.randomBytes(12).toString("hex")}`}encodeHeaderValue(e){return/^[\x00-\x7F]*$/.test(e)?e:`=?UTF-8?Q?${e.replace(/[^\x00-\x7F]/g,t=>{let r=t.charCodeAt(0).toString(16).toUpperCase();return`=${r.length<2?`0${r}`:r}`})}?=`}sanitizeHeader(e){let t=e.replace(/[\r\n\t]+/g," ").replace(/\s{2,}/g," ").trim();return this.encodeHeaderValue(t)}createEmailHeaders(e){let t=this.generateMessageId(),r=new Date().toUTCString(),s=e.from||this.config.user,i=Array.isArray(e.to)?e.to.join(", "):e.to,o=[`From: ${this.sanitizeHeader(s)}`,`To: ${this.sanitizeHeader(i)}`,`Subject: ${this.sanitizeHeader(e.subject)}`,`Message-ID: ${t}`,`Date: ${r}`,"MIME-Version: 1.0"];if(e.cc){let n=Array.isArray(e.cc)?e.cc.join(", "):e.cc;o.push(`Cc: ${this.sanitizeHeader(n)}`)}if(e.replyTo&&o.push(`Reply-To: ${this.sanitizeHeader(e.replyTo)}`),e.headers)for(let[n,l]of Object.entries(e.headers))/^[a-zA-Z0-9-]+$/.test(n)&&(/^(from|to|cc|bcc|subject|date|message-id)$/i.test(n)||o.push(`${n}: ${this.sanitizeHeader(l)}`));return o}createMultipartEmail(e){let{text:t,html:r}=e,s=this.createEmailHeaders(e),i=this.generateBoundary();return r&&t?(s.push(`Content-Type: multipart/alternative; boundary="${i}"`),`${s.join(`\r | |
| `)}\r | |
| \r | |
| --${i}\r | |
| Content-Type: text/plain; charset=utf-8\r | |
| \r | |
| ${t||""}\r | |
| \r | |
| --${i}\r | |
| Content-Type: text/html; charset=utf-8\r | |
| \r | |
| ${r||""}\r | |
| \r | |
| --${i}--\r | |
| `):(s.push("Content-Type: text/html; charset=utf-8"),r?`${s.join(`\r | |
| `)}\r | |
| \r | |
| ${r}`:`${s.join(`\r | |
| `)}\r | |
| \r | |
| ${t||""}`)}async smtpHandshake(){let e=await this.sendCommand(`EHLO ${this.config.clientName}`,250);if(this.parseCapabilities(e),!this.secureMode&&this.serverCapabilities.includes("STARTTLS")){await this.sendCommand("STARTTLS",220),await this.upgradeToTLS();let t=await this.sendCommand(`EHLO ${this.config.clientName}`,250);this.parseCapabilities(t)}this.config.skipAuthentication?this.log("Authentication skipped (testing mode)"):await this.authenticate()}async sendEmail(e){let t=e.from||this.config.user,{to:r,subject:s}=e,i=e.text||"",o=e.html||"";if(!t||!r||!s||!i&&!o)return{success:!1,error:"Missing required email parameters (from, to, subject, and either text or html)"};if(!N(t))return{success:!1,error:"Invalid email address format"};let n=Array.isArray(e.to)?e.to:[e.to];for(let l of n)if(!N(l))return{success:!1,error:`Invalid recipient email address format: ${l}`};for(this.retryCount=0;this.retryCount<=this.config.maxRetries;this.retryCount++)try{this.retryCount>0&&(this.log(`Retrying email send (attempt ${this.retryCount} of ${this.config.maxRetries})...`),await new Promise(u=>setTimeout(u,this.config.retryDelay))),this.connected||(await this.connect(),await this.smtpHandshake()),await this.sendCommand(`MAIL FROM:<${t}>`,250);for(let u of n)await this.sendCommand(`RCPT TO:<${u}>`,250);if(e.cc){let u=Array.isArray(e.cc)?e.cc:[e.cc];for(let h of u)N(h)&&await this.sendCommand(`RCPT TO:<${h}>`,250)}if(e.bcc){let u=Array.isArray(e.bcc)?e.bcc:[e.bcc];for(let h of u)N(h)&&await this.sendCommand(`RCPT TO:<${h}>`,250)}await this.sendCommand("DATA",354);let l=this.createMultipartEmail(e);if(l.length>this.maxEmailSize)return{success:!1,error:"Email size exceeds maximum allowed"};await this.sendCommand(`${l}\r | |
| .`,250);let a=/Message-ID: (.*)/i.exec(l);return{success:!0,messageId:a?a[1].trim():void 0,message:"Email sent successfully"}}catch(l){let a=l.message;if(this.log(`Error sending email: ${a}`,!0),a.includes("5.")||a.includes("Authentication failed")||a.includes("certificate")||this.retryCount>=this.config.maxRetries)return{success:!1,error:a};try{this.connected&&await this.sendCommand("RSET",250)}catch{}this.socket?.end(),this.connected=!1}return{success:!1,error:"Maximum retry count exceeded"}}async close(){try{this.connected&&await this.sendCommand("QUIT",221)}catch(e){this.log(`Error during QUIT: ${e.message}`,!0)}finally{this.socket&&(this.socket.destroy(),this.socket=null,this.connected=!1)}}};var Y=class{smtpClient;constructor(e){let t=new de(e).get(),r=new ge(t);this.smtpClient=r}async send(e){try{let t=Array.isArray(e.to)?e.to:[e.to];for(let s of t)await fe(s)||console.error(`Warning: No MX records found for recipient domain: ${s}`);let r=await this.smtpClient.sendEmail(e);r.success?console.log(`Message ID: ${r.messageId}`):console.error(`Failed to send email: ${r.error}`),await this.smtpClient.close()}catch(t){console.error("Error in email sending process:",t.message)}}};var D=()=>{let e=Qe(process.env.DEBUG)||!1;return{auth:{jwtSecret:process.env.AUTH_JWT_SECRET||"your-jwt-secret",magicLinkExpirySeconds:900,jwtExpirySeconds:3600,refreshTokenExpirySeconds:604800,maxActiveSessions:3,appUrl:process.env.APP_URL||"http://localhost:3000",templates:null,debug:e},email:{emailSubject:"Your Secure Login Link",user:process.env.EMAIL_USER||"",host:process.env.EMAIL_HOST||"",password:process.env.EMAIL_PASSWORD||"",port:465,secure:!0,maxRetries:2,debug:e},storage:{databaseDirectory:"mikroauth",encryptionKey:process.env.STORAGE_KEY||"",debug:e},server:{port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"],debug:e}}};function Qe(e){return e==="true"||e===!0}var et=class{algorithm="HS256";secret="HS256";constructor(e){if(process.env.NODE_ENV==="production"&&(!e||e.length<32||e===D().auth.jwtSecret))throw new Error("Production environment requires a strong JWT secret (min 32 chars)");this.secret=e}sign(e,t={}){let r={alg:this.algorithm,typ:"JWT"},s=Math.floor(Date.now()/1e3),i={...e,iat:s};t.exp!==void 0&&(i.exp=s+t.exp),t.notBefore!==void 0&&(i.nbf=s+t.notBefore),t.issuer&&(i.iss=t.issuer),t.audience&&(i.aud=t.audience),t.subject&&(i.sub=t.subject),t.jwtid&&(i.jti=t.jwtid);let o=this.base64UrlEncode(JSON.stringify(r)),n=this.base64UrlEncode(JSON.stringify(i)),l=`${o}.${n}`,a=this.createSignature(l);return`${l}.${a}`}verify(e,t={}){let r=this.decode(e);if(r.header.alg!==this.algorithm)throw new Error(`Invalid algorithm. Expected ${this.algorithm}, got ${r.header.alg}`);let[s,i]=e.split("."),o=`${s}.${i}`;if(this.createSignature(o)!==r.signature)throw new Error("Invalid signature");let n=r.payload,l=Math.floor(Date.now()/1e3),a=t.clockTolerance||0;if(n.exp!==void 0&&n.exp+a<l)throw new Error("Token expired");if(n.nbf!==void 0&&n.nbf-a>l)throw new Error("Token not yet valid");if(t.issuer&&n.iss!==t.issuer)throw new Error("Invalid issuer");if(t.audience&&n.aud!==t.audience)throw new Error("Invalid audience");if(t.subject&&n.sub!==t.subject)throw new Error("Invalid subject");return n}decode(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid token format");try{let[r,s,i]=t,o=JSON.parse(this.base64UrlDecode(r)),n=JSON.parse(this.base64UrlDecode(s));return{header:o,payload:n,signature:i}}catch{throw new Error("Failed to decode token")}}createSignature(e){let t=A.createHmac("sha256",this.secret).update(e).digest();return this.base64UrlEncode(t)}base64UrlEncode(e){let t;return typeof e=="string"?t=Buffer.from(e):t=e,t.toString("base64").replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")}base64UrlDecode(e){let t=e.replace(/-/g,"+").replace(/_/g,"/");switch(t.length%4){case 0:break;case 2:t+="==";break;case 3:t+="=";break;default:throw new Error("Invalid base64 string")}return Buffer.from(t,"base64").toString()}},tt=class{templates;constructor(e){e?this.templates=e:this.templates=rt}getText(e,t){return this.templates.textVersion(e,t).trim()}getHtml(e,t){return this.templates.htmlVersion(e,t).trim()}},rt={textVersion:(e,t)=>` | |
| Click this link to login: ${e} | |
| Security Information: | |
| - Expires in ${t} minutes | |
| - Can only be used once | |
| - Should only be used by you | |
| If you didn't request this link, please ignore this email. | |
| `,htmlVersion:(e,t)=>` | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Your Login Link</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| line-height: 1.6; | |
| color: #333; | |
| max-width: 600px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .container { | |
| border: 1px solid #e1e1e1; | |
| border-radius: 5px; | |
| padding: 20px; | |
| } | |
| .button { | |
| display: inline-block; | |
| background-color: #4CAF50; | |
| color: white; | |
| text-decoration: none; | |
| padding: 10px 20px; | |
| border-radius: 5px; | |
| margin: 20px 0; | |
| } | |
| .security-info { | |
| background-color: #f8f8f8; | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin-top: 20px; | |
| } | |
| .footer { | |
| margin-top: 20px; | |
| font-size: 12px; | |
| color: #888; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h2>Your Secure Login Link</h2> | |
| <p>Click the button below to log in to your account:</p> | |
| <a href="${e}" class="button">Login to Your Account</a> | |
| <p> | |
| Hello, this is a test email! Hall\xE5, MikroMail has international support for, among others, espa\xF1ol, fran\xE7ais, portugu\xEAs, \u4E2D\u6587, \u65E5\u672C\u8A9E, and \u0420\u0443\u0441\u0441\u043A\u0438\u0439! | |
| </p> | |
| <div class="security-info"> | |
| <h3>Security Information:</h3> | |
| <ul> | |
| <li>This link expires in ${t} minutes</li> | |
| <li>Can only be used once</li> | |
| <li>Should only be used by you</li> | |
| </ul> | |
| </div> | |
| <p>If you didn't request this link, please ignore this email.</p> | |
| <div class="footer"> | |
| <p>This is an automated message, please do not reply to this email.</p> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| `},st=class{constructor(e){this.options=e}sentEmails=[];async sendMail(e){this.sentEmails.push(e),this.options?.logToConsole&&(console.log("Email sent:"),console.log(`From: ${e.from}`),console.log(`To: ${e.to}`),console.log(`Subject: ${e.subject}`),console.log(`Text: ${e.text}`)),this.options?.onSend&&this.options.onSend(e)}getSentEmails(){return[...this.sentEmails]}clearSentEmails(){this.sentEmails=[]}},it=class{data=new Map;collections=new Map;expiryEmitter=new Ze;expiryCheckInterval;constructor(e=1e3){this.expiryCheckInterval=setInterval(()=>this.checkExpiredItems(),e)}destroy(){clearInterval(this.expiryCheckInterval),this.data.clear(),this.collections.clear(),this.expiryEmitter.removeAllListeners()}checkExpiredItems(){let e=Date.now();for(let[t,r]of this.data.entries())r.expiry&&r.expiry<e&&(this.data.delete(t),this.expiryEmitter.emit("expired",t));for(let[t,r]of this.collections.entries())r.expiry&&r.expiry<e&&(this.collections.delete(t),this.expiryEmitter.emit("expired",t))}async set(e,t,r){let s=r?Date.now()+r*1e3:null;this.data.set(e,{value:t,expiry:s})}async get(e){let t=this.data.get(e);return t?t.expiry&&t.expiry<Date.now()?(this.data.delete(e),null):t.value:null}async delete(e){this.data.delete(e),this.collections.delete(e)}async addToCollection(e,t,r){this.collections.has(e)||this.collections.set(e,{items:[],expiry:r?Date.now()+r*1e3:null});let s=this.collections.get(e);r&&(s.expiry=Date.now()+r*1e3),s.items.push(t)}async removeFromCollection(e,t){let r=this.collections.get(e);r&&(r.items=r.items.filter(s=>s!==t))}async getCollection(e){let t=this.collections.get(e);return t?[...t.items]:[]}async getCollectionSize(e){let t=this.collections.get(e);return t?t.items.length:0}async removeOldestFromCollection(e){let t=this.collections.get(e);return!t||t.items.length===0?null:t.items.shift()||null}async findKeys(e){let t=e.replace(/\*/g,".*").replace(/\?/g,"."),r=new RegExp(`^${t}$`),s=Array.from(this.data.keys()).filter(o=>r.test(o)),i=Array.from(this.collections.keys()).filter(o=>r.test(o));return[...new Set([...s,...i])]}};function at(e){if(!e||e.trim()===""||(e.match(/@/g)||[]).length!==1)return!1;let[t,r]=e.split("@");return!(!t||!r||e.includes("..")||!nt(t)||!ot(r))}function nt(e){return e.startsWith('"')&&e.endsWith('"')?!e.slice(1,-1).includes('"'):e.length>64||e.startsWith(".")||e.endsWith(".")?!1:/^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$/.test(e)}function ot(e){if(e.startsWith("[")&&e.endsWith("]")){let r=e.slice(1,-1);return r.startsWith("IPv6:")?ct(r.slice(5)):lt(r)}let t=e.split(".");if(t.length===0)return!1;for(let r of t)if(!r||r.length>63||!/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(r))return!1;if(t.length>1){let r=t[t.length-1];if(!/^[a-zA-Z]{2,}$/.test(r))return!1}return!0}function lt(e){return/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/.test(e)}function ct(e){if(!/^[a-fA-F0-9:]+$/.test(e))return!1;let t=e.split(":");return!(t.length<2||t.length>8)}var g=D(),ut=e=>{let t={configFilePath:"mikroauth.config.json",args:process.argv,options:[{flag:"--jwtSecret",path:"auth.jwtSecret",defaultValue:g.auth.jwtSecret},{flag:"--magicLinkExpirySeconds",path:"auth.magicLinkExpirySeconds",defaultValue:g.auth.magicLinkExpirySeconds},{flag:"--jwtExpirySeconds",path:"auth.jwtExpirySeconds",defaultValue:g.auth.jwtExpirySeconds},{flag:"--refreshTokenExpirySeconds",path:"auth.refreshTokenExpirySeconds",defaultValue:g.auth.refreshTokenExpirySeconds},{flag:"--maxActiveSessions",path:"auth.maxActiveSessions",defaultValue:g.auth.maxActiveSessions},{flag:"--appUrl",path:"auth.appUrl",defaultValue:g.auth.appUrl},{flag:"--debug",path:"auth.debug",isFlag:!0,defaultValue:g.auth.debug},{flag:"--emailSubject",path:"email.emailSubject",defaultValue:"Your Secure Login Link"},{flag:"--emailHost",path:"email.host",defaultValue:g.email.host},{flag:"--emailUser",path:"email.user",defaultValue:g.email.user},{flag:"--emailPassword",path:"email.password",defaultValue:g.email.password},{flag:"--emailPort",path:"email.port",defaultValue:g.email.port},{flag:"--emailSecure",path:"email.secure",isFlag:!0,defaultValue:g.email.secure},{flag:"--emailMaxRetries",path:"email.maxRetries",defaultValue:g.email.maxRetries},{flag:"--debug",path:"email.debug",isFlag:!0,defaultValue:g.email.debug},{flag:"--dir",path:"storage.databaseDirectory",defaultValue:g.storage.databaseDirectory},{flag:"--encryptionKey",path:"storage.encryptionKey",defaultValue:g.storage.encryptionKey},{flag:"--debug",path:"storage.debug",defaultValue:g.storage.debug},{flag:"--port",path:"server.port",defaultValue:g.server.port},{flag:"--host",path:"server.host",defaultValue:g.server.host},{flag:"--https",path:"server.useHttps",isFlag:!0,defaultValue:g.server.useHttps},{flag:"--https",path:"server.useHttp2",isFlag:!0,defaultValue:g.server.useHttp2},{flag:"--cert",path:"server.sslCert",defaultValue:g.server.sslCert},{flag:"--key",path:"server.sslKey",defaultValue:g.server.sslKey},{flag:"--ca",path:"server.sslCa",defaultValue:g.server.sslCa},{flag:"--ratelimit",path:"server.rateLimit.enabled",defaultValue:g.server.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"server.rateLimit.requestsPerMinute",defaultValue:g.server.rateLimit.requestsPerMinute},{flag:"--allowed",path:"server.allowedDomains",defaultValue:g.server.allowedDomains,parser:T.array},{flag:"--debug",path:"server.debug",isFlag:!0,defaultValue:g.server.debug}]};return e&&(t.config=e),t},H={linkSent:"If a matching account was found, a magic link has been sent.",revokedSuccess:"All other sessions revoked successfully.",logoutSuccess:"Logged out successfully."},pe=class{config;email;storage;jwtService;templates;constructor(e,t,r){let s=new k(ut({auth:e})).get().auth;s.debug&&console.log("Using configuration:",s),this.config=s,this.email=t||new st,this.storage=r||new it,this.jwtService=new et(s.jwtSecret),this.templates=new tt(s?.templates),this.checkIfUsingDefaultCredentialsInProduction()}checkIfUsingDefaultCredentialsInProduction(){process.env.NODE_ENV==="production"&&this.config.jwtSecret===D().auth.jwtSecret&&(console.error("WARNING: Using default secrets in production environment!"),process.exit(1))}generateToken(e){let t=Date.now().toString(),r=A.randomBytes(32).toString("hex");return A.createHash("sha256").update(`${e}:${t}:${r}`).digest("hex")}generateJsonWebToken(e){return this.jwtService.sign({sub:e.id,email:e.email,username:e.username,role:e.role,exp:Math.floor(Date.now()/1e3)+60*60*24})}generateRefreshToken(){return A.randomBytes(40).toString("hex")}async trackSession(e,t,r){let s=`sessions:${e}`;if(await this.storage.getCollectionSize(s)>=this.config.maxActiveSessions){let i=await this.storage.removeOldestFromCollection(s);i&&await this.storage.delete(`refresh:${i}`)}await this.storage.addToCollection(s,t,this.config.refreshTokenExpirySeconds),await this.storage.set(`refresh:${t}`,JSON.stringify(r),this.config.refreshTokenExpirySeconds)}generateMagicLinkUrl(e){let{token:t,email:r}=e;try{return new Ye(this.config.appUrl),`${this.config.appUrl}?token=${encodeURIComponent(t)}&email=${encodeURIComponent(r)}`}catch{throw new Error("Invalid base URL configuration")}}async createMagicLink(e){let{email:t,ip:r}=e;if(!at(t))throw new Error("Valid email required");try{let s=this.generateToken(t),i=`magic_link:${s}`,o={email:t,ipAddress:r||"unknown",createdAt:Date.now()};await this.storage.set(i,JSON.stringify(o),this.config.magicLinkExpirySeconds);let n=await this.storage.findKeys("magic_link:*");for(let c of n){if(c===i)continue;let u=await this.storage.get(c);if(u)try{JSON.parse(u).email===t&&await this.storage.delete(c)}catch{}}let l=this.generateMagicLinkUrl({token:s,email:t}),a=Math.ceil(this.config.magicLinkExpirySeconds/60);return await this.email.sendMail({from:process.env.EMAIL_FROM||D().email.user,to:t,subject:process.env.EMAIL_SUBJECT||D().email.emailSubject,text:this.templates.getText(l,a),html:this.templates.getHtml(l,a)}),{message:H.linkSent}}catch(s){throw console.error(`Failed to process magic link request: ${s}`),new Error("Failed to process magic link request")}}async verifyToken(e){let{token:t,email:r}=e;try{let s=`magic_link:${t}`,i=await this.storage.get(s);if(!i)throw new Error("Invalid or expired token");let o=JSON.parse(i);if(o.email!==r)throw new Error("Email mismatch");let n=o.username,l=o.role;await this.storage.delete(s);let a=A.randomBytes(16).toString("hex"),c=this.generateRefreshToken(),u={sub:r,username:n,role:l,jti:a,lastLogin:o.createdAt,metadata:{ip:o.ipAddress},exp:Math.floor(Date.now()/1e3)+60*60*24},h=this.jwtService.sign(u,{exp:this.config.jwtExpirySeconds});return await this.trackSession(r,c,{...o,tokenId:a,createdAt:Date.now()}),{accessToken:h,refreshToken:c,exp:this.config.jwtExpirySeconds,tokenType:"Bearer"}}catch(s){throw console.error("Token verification error:",s),new Error("Verification failed")}}async refreshAccessToken(e){try{let t=await this.storage.get(`refresh:${e}`);if(!t)throw new Error("Invalid or expired refresh token");let r=JSON.parse(t),s=r.email;if(!s)throw new Error("Invalid refresh token data");let i=r.username,o=r.role,n=A.randomBytes(16).toString("hex"),l={sub:s,username:i,role:o,jti:n,lastLogin:r.lastLogin||r.createdAt,metadata:{ip:r.ipAddress}},a=this.jwtService.sign(l,{exp:this.config.jwtExpirySeconds});return r.lastUsed=Date.now(),await this.storage.set(`refresh:${e}`,JSON.stringify(r),this.config.refreshTokenExpirySeconds),{accessToken:a,refreshToken:e,exp:this.config.jwtExpirySeconds,tokenType:"Bearer"}}catch(t){throw console.error("Token refresh error:",t),new Error("Token refresh failed")}}verify(e){try{return this.jwtService.verify(e)}catch{throw new Error("Invalid token")}}async logout(e){try{if(!e||typeof e!="string")throw new Error("Refresh token is required");let t=await this.storage.get(`refresh:${e}`);if(!t)return{message:H.logoutSuccess};let r=JSON.parse(t).email;if(!r)throw new Error("Invalid refresh token data");await this.storage.delete(`refresh:${e}`);let s=`sessions:${r}`;return await this.storage.removeFromCollection(s,e),{message:H.logoutSuccess}}catch(t){throw console.error("Logout error:",t),new Error("Logout failed")}}async getSessions(e){try{if(!e.user?.email)throw new Error("User not authenticated");let t=e.user.email,r=e.body?.refreshToken,s=`sessions:${t}`,i=(await this.storage.getCollection(s)).map(async n=>{try{let l=await this.storage.get(`refresh:${n}`);if(!l)return await this.storage.removeFromCollection(s,n),null;let a=JSON.parse(l);return{id:`${n.substring(0,8)}...`,createdAt:a.createdAt||0,lastLogin:a.lastLogin||a.createdAt||0,lastUsed:a.lastUsed||a.createdAt||0,metadata:{ip:a.ipAddress},isCurrentSession:n===r}}catch{return await this.storage.removeFromCollection(s,n),null}}),o=(await Promise.all(i)).filter(Boolean);return o.sort((n,l)=>l.createdAt-n.createdAt),{sessions:o}}catch(t){throw console.error("Get sessions error:",t),new Error("Failed to fetch sessions")}}async revokeSessions(e){try{if(!e.user?.email)throw new Error("User not authenticated");let t=e.user.email,r=e.body?.refreshToken,s=`sessions:${t}`,i=await this.storage.getCollection(s);for(let o of i)r&&o===r||await this.storage.delete(`refresh:${o}`);return await this.storage.delete(s),r&&await this.storage.get(`refresh:${r}`)&&await this.storage.addToCollection(s,r,this.config.refreshTokenExpirySeconds),{message:H.revokedSuccess}}catch(t){throw console.error("Revoke sessions error:",t),new Error("Failed to revoke sessions")}}authenticate(e,t){try{let r=e.headers?.authorization;if(!r||!r.startsWith("Bearer "))throw new Error("Authentication required");let s=r.split(" ")[1];try{let i=this.verify(s);e.user={email:i.sub},t()}catch{throw new Error("Invalid or expired token")}}catch(r){t(r)}}},ye=class{db;PREFIX_KV="kv:";PREFIX_COLLECTION="coll:";TABLE_NAME="mikroauth";constructor(e){this.db=e}async start(){await this.db.start()}async close(){await this.db.close()}async set(e,t,r){let s=`${this.PREFIX_KV}${e}`,i=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:t,expiration:i})}async get(e){let t=`${this.PREFIX_KV}${e}`;return await this.db.get({tableName:this.TABLE_NAME,key:t})||null}async delete(e){let t=`${this.PREFIX_KV}${e}`;await this.db.delete({tableName:this.TABLE_NAME,key:t})}async addToCollection(e,t,r){let s=`${this.PREFIX_COLLECTION}${e}`,i=await this.db.get({tableName:this.TABLE_NAME,key:s}),o=[];i&&(o=JSON.parse(i)),o.includes(t)||o.push(t);let n=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:JSON.stringify(o),expiration:n})}async removeFromCollection(e,t){let r=`${this.PREFIX_COLLECTION}${e}`,s=await this.db.get({tableName:this.TABLE_NAME,key:r});if(!s)return;let i=JSON.parse(s);i=i.filter(o=>o!==t),await this.db.write({tableName:this.TABLE_NAME,key:r,value:JSON.stringify(i)})}async getCollection(e){let t=`${this.PREFIX_COLLECTION}${e}`,r=await this.db.get({tableName:this.TABLE_NAME,key:t});return r?JSON.parse(r):[]}async getCollectionSize(e){return(await this.getCollection(e)).length}async removeOldestFromCollection(e){let t=`${this.PREFIX_COLLECTION}${e}`,r=await this.db.get({tableName:this.TABLE_NAME,key:t});if(!r)return null;let s=JSON.parse(r);if(s.length===0)return null;let i=s.shift();return await this.db.write({tableName:this.TABLE_NAME,key:t,value:JSON.stringify(s)}),i}async findKeys(e){let t=e.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/\?/g,"."),r=new RegExp(`^${t}$`);return(await this.db.get({tableName:this.TABLE_NAME})).filter(s=>{let i=s[0];return typeof i=="string"&&i.startsWith(this.PREFIX_KV)}).map(s=>s[0].substring(this.PREFIX_KV.length)).filter(s=>r.test(s))}},we=class{email;sender;constructor(e){this.sender=e.user,this.email=new Y({config:e})}async sendMail(e){await this.email.send({from:this.sender,to:e.to,cc:e.cc,bcc:e.bcc,subject:e.subject,text:e.text,html:e.html})}};var O=class extends Error{constructor(e){super(),this.name="NotFoundError",this.message=e||"Resource not found",this.cause={statusCode:404}}},M=class extends Error{constructor(e){super(),this.name="CheckpointError",this.message=e,this.cause={statusCode:500}}};var v=()=>Date.now(),be=(e,t)=>t==="D"||e.length===0||e.join(" ")==="null"?null:ve(e),ve=e=>{try{return JSON.parse(e.join(" "))}catch{return}};function Z(e){return e==="true"||e===!0}function Ee(e){let t=(n,l)=>{if(n){if(l==="json")return ve(n)||n;if(l==="number")return Number.parseInt(n,10)}},r=t(e?.filter,"json"),s=t(e?.sort,"json"),i=t(e?.limit,"number"),o=t(e?.offset,"number");if(!(!r&&!s&&!i&&!o))return{filter:r,sort:s,limit:i,offset:o}}import{existsSync as Q}from"node:fs";import{readFile as Ce,unlink as ht,writeFile as Se}from"node:fs/promises";var ke=class{checkpointInterval;defaultCheckpointIntervalMs=10*1e3;isCheckpointing=!1;lastCheckpointTime=0;walFile;checkpointTimer=null;wal;table;constructor(e){let{table:t,wal:r,walFile:s,checkpointIntervalMs:i}=e;this.defaultCheckpointIntervalMs=i||this.defaultCheckpointIntervalMs,this.table=t,this.wal=r,this.walFile=s,this.checkpointInterval=i,this.lastCheckpointTime=v()}async start(){let e=`${this.walFile}.checkpoint`;if(Q(e)){console.log("Incomplete checkpoint detected, running recovery...");try{let t=await Ce(e,"utf8");console.log(`Incomplete checkpoint from: ${new Date(Number.parseInt(t))}`),await this.checkpoint(!0)}catch(t){throw new O(`Error reading checkpoint file: ${t}`)}}this.checkpointTimer=setInterval(async()=>{try{await this.checkpoint()}catch(t){throw new M(`Checkpoint interval failed: ${t}`)}},this.checkpointInterval)}stop(){this.checkpointTimer&&(clearInterval(this.checkpointTimer),this.checkpointTimer=null),this.isCheckpointing=!1}async checkpoint(e=!1){if(this.isCheckpointing)return;let t=v();if(!(!e&&t-this.lastCheckpointTime<this.checkpointInterval)){this.isCheckpointing=!0;try{await this.wal.flushWAL();let r=`${this.walFile}.checkpoint`;await Se(r,t.toString(),"utf8");let s=await this.getTablesFromWAL();await this.persistTables(s);try{await Se(this.walFile,"","utf8"),process.env.DEBUG==="true"&&console.log("WAL truncated successfully")}catch(i){throw new M(`Failed to truncate WAL: ${i}`)}Q(r)&&await ht(r),this.wal.clearPositions(),this.lastCheckpointTime=t,process.env.DEBUG==="true"&&console.log("Checkpoint complete")}catch(r){throw new M(`Checkpoint failed: ${r}`)}finally{this.isCheckpointing=!1}}}async getTablesFromWAL(){let e=new Set;if(!Q(this.walFile))return e;try{let t=await Ce(this.walFile,"utf8");if(!t.trim())return e;let r=t.trim().split(` | |
| `);for(let s of r){if(!s.trim())continue;let i=s.split(" ");i.length>=3&&e.add(i[2])}}catch(t){throw new M(`Error reading WAL file: ${t}`)}return e}async persistTables(e){let t=Array.from(e).map(async r=>{try{await this.table.flushTableToDisk(r),console.log(`Checkpointed table "${r}"`)}catch(s){throw new M(`Failed to checkpoint table "${r}": ${s.message}`)}});await Promise.all(t)}};import{existsSync as Te,statSync as ee,writeFileSync as dt}from"node:fs";import{appendFile as ft,readFile as xe}from"node:fs/promises";var Ie=class{walFile;walInterval;walBuffer=[];maxWalBufferEntries=Number.parseInt(process.env.MAX_WAL_BUFFER_ENTRIES||"100");maxWalBufferSize=Number.parseInt(process.env.MAX_WAL_BUFFER_SIZE||(1024*1024*.01).toString());maxWalSizeBeforeCheckpoint=Number.parseInt(process.env.MAX_WAL_BUFFER_SIZE||(1024*1024*.01).toString());lastProcessedEntryCount=new Map;checkpointCallback=null;walTimer=null;constructor(e,t){this.walFile=e,this.walInterval=t,this.start()}setCheckpointCallback(e){this.checkpointCallback=e}start(){Te(this.walFile)||dt(this.walFile,"","utf-8"),this.walTimer=setInterval(async()=>{try{this.walBuffer.length>0&&await this.flushWAL()}catch(e){console.error("WAL flush interval failed:",e)}},this.walInterval)}stop(){this.walTimer&&(clearInterval(this.walTimer),this.walTimer=null)}checkWalFileExists(){if(!Te(this.walFile))throw new O(`WAL file "${this.walFile}" does not exist`)}async loadWAL(e){this.checkWalFileExists();let t=[];if((ee(this.walFile)?.size||0)===0)return t;try{let i=(await xe(this.walFile,"utf8")).trim().split(` | |
| `),o=v();for(let n=0;n<i.length;n++){if(!i[n].trim())continue;let a=this.lastProcessedEntryCount.get(e||"")||0;if(e&&n<a)continue;let[c,u,h,d,f,y,...b]=i[n].split(" ");if(e&&h!==e)continue;let S=Number(d.split(":")[1]),p=f==="0"?null:Number(f.split(":")[1]);if(p&&p<o)continue;let w=be(b,u);w!==void 0&&(e&&this.lastProcessedEntryCount.set(e,n+1),u==="W"?t.push({operation:"W",tableName:h,key:y,data:{value:w,version:S,timestamp:o,expiration:p}}):u==="D"&&t.push({operation:"D",tableName:h,key:y}))}return t}catch(s){return console.error(e?`Failed to replay WAL for table "${e}": ${s.message}`:`Failed to replay WAL: ${s.message}`),t}}async hasNewWALEntriesForTable(e){this.checkWalFileExists();try{let t=await xe(this.walFile,"utf8");if(!t.trim())return!1;let r=t.trim().split(` | |
| `),s=this.lastProcessedEntryCount.get(e)||0;if(s>=r.length)return!1;for(let i=s;i<r.length;i++){let o=r[i];if(!o.trim())continue;let n=o.split(" ");if(n.length>=3&&n[2]===e)return!0}return!1}catch(t){return console.error(`Error checking WAL for ${e}:`,t),!0}}async flushWAL(){if(this.checkWalFileExists(),this.walBuffer.length===0)return;let e=[...this.walBuffer];this.walBuffer=[];try{await ft(this.walFile,e.join(""),"utf8");let t=ee(this.walFile);t.size>this.maxWalSizeBeforeCheckpoint&&(process.env.DEBUG==="true"&&console.log(`WAL size (${t.size}) exceeds limit (${this.maxWalSizeBeforeCheckpoint}), triggering checkpoint`),this.checkpointCallback&&setImmediate(async()=>{try{await this.checkpointCallback()}catch(r){console.error("Error during automatic checkpoint:",r)}}))}catch(t){throw console.error(`Failed to flush WAL: ${t.message}`),this.walBuffer=[...e,...this.walBuffer],t}}async appendToWAL(e,t,r,s,i,o=0){this.checkWalFileExists();let l=`${v()} ${t} ${e} v:${i} x:${o} ${r} ${JSON.stringify(s)} | |
| `;this.walBuffer.push(l),this.walBuffer.length>=this.maxWalBufferEntries&&await this.flushWAL(),this.walBuffer.reduce((u,h)=>u+h.length,0)>=this.maxWalBufferSize&&await this.flushWAL();let c=ee(this.walFile);c.size>this.maxWalSizeBeforeCheckpoint&&(process.env.DEBUG==="true"&&console.log(`WAL size (${c.size}) exceeds limit (${this.maxWalSizeBeforeCheckpoint}), triggering checkpoint`),this.checkpointCallback&&setImmediate(async()=>{try{await this.checkpointCallback()}catch(u){console.error("Error during automatic checkpoint:",u)}}))}clearPositions(){this.lastProcessedEntryCount.clear()}};var L=()=>({db:{dbName:"mikrodb",databaseDirectory:"mikrodb",walFileName:"wal.log",walInterval:2e3,encryptionKey:"",maxWriteOpsBeforeFlush:100,debug:Z(process.env.DEBUG)||!1},events:{},server:{port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"],debug:Z(process.env.DEBUG)||!1}});import{createCipheriv as mt,createDecipheriv as gt,randomBytes as pt,scryptSync as yt}from"node:crypto";var W=class{algo="aes-256-gcm";KEY_LENGTH=32;IV_LENGTH=12;generateKey(e,t){return yt(`${t}#${e}`,t,this.KEY_LENGTH)}generateIV(){return pt(this.IV_LENGTH)}encrypt(e,t,r){if(t.length!==this.KEY_LENGTH)throw new Error(`Invalid key length: ${t.length} bytes. Expected: ${this.KEY_LENGTH} bytes`);if(r.length!==this.IV_LENGTH)throw new Error(`Invalid IV length: ${r.length} bytes. Expected: ${this.IV_LENGTH} bytes`);let s=mt(this.algo,t,r),i=Buffer.concat([s.update(e,"utf8"),s.final()]),o=s.getAuthTag();return{iv:r,encrypted:i,authTag:o}}decrypt(e,t){let{iv:r,encrypted:s,authTag:i}=e;if(t.length!==this.KEY_LENGTH)throw new Error(`Invalid key length: ${t.length} bytes. Expected: ${this.KEY_LENGTH} bytes`);if(r.length!==this.IV_LENGTH)throw new Error(`Invalid IV length: ${r.length} bytes. Expected: ${this.IV_LENGTH} bytes`);let o=gt(this.algo,t,r);return o.setAuthTag(i),Buffer.concat([o.update(s),o.final()]).toString("utf8")}serialize(e){return Buffer.concat([Buffer.from([1]),Buffer.from([e.iv.length]),e.iv,Buffer.from([e.authTag.length]),e.authTag,e.encrypted])}deserialize(e){let t=0,r=e[t++];if(r!==1)throw new Error(`Unsupported encryption format version: ${r}`);let s=e[t++],i=e.subarray(t,t+s);t+=s;let o=e[t++],n=e.subarray(t,t+o);t+=o;let l=e.subarray(t);return{iv:i,authTag:n,encrypted:l}}toHex(e){return e.toString("hex")}toUtf8(e){return e.toString("utf8")}toBuffer(e){return Buffer.from(e,"hex")}};var _=class{readTableFromBinaryBuffer(e){if(e.length<8||e[0]!==77||e[1]!==68||e[2]!==66||e[3]!==1)throw new Error("Invalid table file format");let t=e.readUInt32LE(4),r=new Map,s=8,i=v();for(let o=0;o<t&&s+26<=e.length;o++){let n=e.readUInt16LE(s);s+=2;let l=e.readUInt32LE(s);s+=4;let a=e.readUInt32LE(s);s+=4;let c=Number(e.readBigUInt64LE(s));s+=8;let u=Number(e.readBigUInt64LE(s));if(s+=8,s+n+l>e.length)break;if(u&&u<=i){s+=n+l;continue}let h=e.toString("utf8",s,s+n);s+=n;let d=e.slice(s,s+l);s+=l;let f=this.decodeValue(d);r.set(h,{value:f,version:a,timestamp:c,expiration:u||null})}return r}toBinaryBuffer(e){let t=[],r=Buffer.from([77,68,66,1]);t.push(r);let s=Array.from(e.entries()).filter(([o])=>typeof o=="string"),i=Buffer.alloc(4);i.writeUInt32LE(s.length,0),t.push(i);for(let[o,n]of s){if(o===null||typeof o!="string")continue;let l=Buffer.from(o),a=this.encodeValue(n.value),c=Buffer.alloc(2);c.writeUInt16LE(l.length,0),t.push(c);let u=Buffer.alloc(4);u.writeUInt32LE(a.length,0),t.push(u);let h=Buffer.alloc(4);h.writeUInt32LE(n.version||0,0),t.push(h);let d=Buffer.alloc(8);d.writeBigUInt64LE(BigInt(n.timestamp||0),0),t.push(d);let f=Buffer.alloc(8);f.writeBigUInt64LE(BigInt(n.expiration||0),0),t.push(f),t.push(l),t.push(a)}return Buffer.concat(t)}encodeValue(e){if(e==null)return Buffer.from([0]);if(typeof e=="boolean")return Buffer.from([1,e?1:0]);if(typeof e=="number"){if(Number.isInteger(e)&&e>=-2147483648&&e<=2147483647){let r=Buffer.alloc(5);return r[0]=2,r.writeInt32LE(e,1),r}let t=Buffer.alloc(9);return t[0]=3,t.writeDoubleLE(e,1),t}if(typeof e=="string"){let t=Buffer.from(e,"utf8"),r=Buffer.alloc(5+t.length);return r[0]=4,r.writeUInt32LE(t.length,1),t.copy(r,5),r}if(Array.isArray(e)){let t=[],r=Buffer.alloc(5);r[0]=5,r.writeUInt32LE(e.length,1),t.push(r);for(let s of e)t.push(this.encodeValue(s));return Buffer.concat(t)}if(typeof e=="object"){if(e instanceof Date){let i=Buffer.alloc(9);return i[0]=7,i.writeBigInt64LE(BigInt(e.getTime()),1),i}let t=Object.keys(e),r=[],s=Buffer.alloc(5);s[0]=6,s.writeUInt32LE(t.length,1),r.push(s);for(let i of t){let o=Buffer.from(i,"utf8"),n=Buffer.alloc(2);n.writeUInt16LE(o.length,0),r.push(n),r.push(o),r.push(this.encodeValue(e[i]))}return Buffer.concat(r)}return this.encodeValue(String(e))}decodeValue(e){if(e.length===0)return null;let t=e[0];switch(t){case 0:return null;case 1:return e[1]===1;case 2:return e.readInt32LE(1);case 3:return e.readDoubleLE(1);case 4:{let r=e.readUInt32LE(1);return e.toString("utf8",5,5+r)}case 5:{let r=e.readUInt32LE(1),s=new Array(r),i=5;for(let o=0;o<r;o++){let{value:n,bytesRead:l}=this.decodeValueWithSize(e,i);s[o]=n,i+=l}return s}case 6:{let r=e.readUInt32LE(1),s={},i=5;for(let o=0;o<r;o++){let n=e.readUInt16LE(i);i+=2;let l=e.toString("utf8",i,i+n);i+=n;let{value:a,bytesRead:c}=this.decodeValueWithSize(e,i);s[l]=a,i+=c}return s}case 7:return new Date(Number(e.readBigInt64LE(1)));default:return console.warn(`Unknown type byte: ${t}`),null}}decodeValueWithSize(e,t=0){if(t>=e.length)return{value:null,bytesRead:0};let r=e[t];switch(r){case 0:return{value:null,bytesRead:1};case 1:return{value:e[t+1]===1,bytesRead:2};case 2:return{value:e.readInt32LE(t+1),bytesRead:5};case 3:return{value:e.readDoubleLE(t+1),bytesRead:9};case 4:{let s=e.readUInt32LE(t+1);return{value:e.toString("utf8",t+5,t+5+s),bytesRead:5+s}}case 5:{let s=e.readUInt32LE(t+1),i=new Array(s),o=t+5;for(let n=0;n<s;n++){let l=this.decodeValueWithSize(e,o);i[n]=l.value,o+=l.bytesRead}return{value:i,bytesRead:o-t}}case 6:{let s=e.readUInt32LE(t+1),i={},o=t+5;for(let n=0;n<s;n++){let l=e.readUInt16LE(o);o+=2;let a=e.toString("utf8",o,o+l);o+=l;let c=this.decodeValueWithSize(e,o);i[a]=c.value,o+=c.bytesRead}return{value:i,bytesRead:o-t}}case 7:return{value:new Date(Number(e.readBigInt64LE(t+1))),bytesRead:9};default:return console.warn(`Unknown type byte: ${r} at offset ${t}`),{value:null,bytesRead:1}}}};import{writeFile as wt}from"node:fs/promises";async function Ae(e,t,r){let s=new W,o=new _().toBinaryBuffer(t);if(!o){console.log("Buffer is empty, skipping...");return}if(r)try{let n=s.generateKey(r,"salt"),l=o.toString("binary"),a=s.generateIV(),c=s.encrypt(l,n,a);o=s.serialize(c)}catch(n){console.error("Encryption failed:",n)}await wt(e,o)}var Me=class{cacheLimit;tableAccessTimes=new Map;constructor(e={}){this.cacheLimit=e.cacheLimit??20}trackTableAccess(e){this.tableAccessTimes.set(e,v())}findTablesForEviction(e){if(e<=this.cacheLimit)return[];let t=e-this.cacheLimit,r=Array.from(this.tableAccessTimes.entries()).sort((s,i)=>s[1]-i[1]).slice(0,t).map(([s])=>s);for(let s of r)this.tableAccessTimes.delete(s);return r}removeTable(e){this.tableAccessTimes.delete(e)}findExpiredItems(e){let t=v(),r=[];for(let[s,i]of e.entries())i.x&&i.x<t&&r.push([s,i]);return r}clear(){this.tableAccessTimes.clear()}};var Le=class{async query(e,t,r=50){let s=new Map;for(let[i,o]of e.entries())if((!t||(typeof t=="function"?t(o.value):this.evaluateFilter(o.value,t)))&&(s.set(i,o.value),r&&s.size>=r))break;return Array.from(s.values())}evaluateCondition(e,t){if(!t||typeof t!="object"||!t.operator)return e===t;let{operator:r,value:s}=t;switch(r){case"eq":return e===s;case"neq":return e!==s;case"gt":return e>s;case"gte":return e>=s;case"lt":return e<s;case"lte":return e<=s;case"in":return Array.isArray(s)&&s.includes(e);case"nin":return Array.isArray(s)&&!s.includes(e);case"like":return typeof e=="string"&&typeof s=="string"&&e.toLowerCase().includes(s.toLowerCase());case"between":return Array.isArray(s)&&s.length===2&&e>=s[0]&&e<=s[1];case"regex":try{let i=new RegExp(s);return typeof e=="string"&&i.test(e)}catch(i){return console.error("Invalid regex pattern:",i),!1}case"contains":return Array.isArray(e)&&e.includes(s);case"containsAll":return Array.isArray(e)&&Array.isArray(s)&&s.every(i=>e.includes(i));case"containsAny":return Array.isArray(e)&&Array.isArray(s)&&s.some(i=>e.includes(i));case"size":return Array.isArray(e)&&e.length===s;default:return!1}}evaluateFilter(e,t){if(e==null)return!1;if("$or"in t)return t.$or.some(r=>this.evaluateFilter(e,r));for(let[r,s]of Object.entries(t))if(!r.startsWith("$"))if(r.includes(".")){let i=r.split("."),o=e;for(let n of i)if(o=o?.[n],o==null)return!1;if(!this.evaluateCondition(o,s))return!1}else if(s&&typeof s=="object"&&!("operator"in s)){let i=e[r];if(i==null||!this.evaluateFilter(i,s))return!1}else{let i=e[r];if(!this.evaluateCondition(i,s))return!1}return!0}};import{existsSync as $e,mkdirSync as vt}from"node:fs";import{readFile as Et,writeFile as Ct}from"node:fs/promises";import{join as re}from"node:path";import{EventEmitter as bt}from"node:events";var te=class{emitter;targets={};options;constructor(e){this.emitter=new bt,e?.maxListeners===0?this.emitter.setMaxListeners(0):this.emitter.setMaxListeners(e?.maxListeners||10),this.options={errorHandler:e?.errorHandler||(t=>console.error(t))}}addTarget(e){return(Array.isArray(e)?e:[e]).map(s=>this.targets[s.name]?(console.error(`Target with name '${s.name}' already exists.`),!1):(this.targets[s.name]={name:s.name,url:s.url,headers:s.headers||{},events:s.events||[]},!0)).every(s=>s===!0)}updateTarget(e,t){if(!this.targets[e])return console.error(`Target with name '${e}' does not exist.`),!1;let r=this.targets[e];return t.url!==void 0&&(r.url=t.url),t.headers&&(r.headers={...r.headers,...t.headers}),t.events&&(r.events=t.events),!0}removeTarget(e){return this.targets[e]?(delete this.targets[e],!0):(console.error(`Target with name '${e}' does not exist.`),!1)}addEventToTarget(e,t){if(!this.targets[e])return console.error(`Target with name '${e}' does not exist.`),!1;let r=Array.isArray(t)?t:[t],s=this.targets[e];return r.forEach(i=>{s.events.includes(i)||s.events.push(i)}),!0}on(e,t){return this.emitter.on(e,t),this}off(e,t){return this.emitter.off(e,t),this}once(e,t){return this.emitter.once(e,t),this}async emit(e,t){let r={success:!0,errors:[]},s=(n,l,a)=>({target:n,event:l,error:a}),i=Object.values(this.targets).filter(n=>n.events.includes(e)||n.events.includes("*"));i.filter(n=>!n.url).forEach(n=>{try{this.emitter.emit(e,t)}catch(l){let a=l instanceof Error?l:new Error(String(l));r.errors.push({target:n.name,event:e,error:a}),this.options.errorHandler(a,e,t),r.success=!1}});let o=i.filter(n=>n.url);if(o.length>0){let n=o.map(async l=>{try{let a=await fetch(l.url,{method:"POST",headers:{"Content-Type":"application/json",...l.headers},body:JSON.stringify({eventName:e,data:t})});if(!a.ok){let c=`HTTP error! Status: ${a.status}: ${a.statusText}`,u=new Error(c);r.errors.push(s(l.name,e,u)),this.options.errorHandler(u,e,t),r.success=!1}}catch(a){let c=a instanceof Error?a:new Error(String(a));r.errors.push(s(l.name,e,c)),this.options.errorHandler(c,e,t),r.success=!1}});await Promise.allSettled(n)}return r}async handleIncomingEvent(e){try{let{eventName:t,data:r}=typeof e=="string"?JSON.parse(e):e;process.nextTick(()=>{try{this.emitter.emit(t,r)}catch(s){this.options.errorHandler(s instanceof Error?s:new Error(String(s)),t,r)}})}catch(t){throw this.options.errorHandler(t instanceof Error?t:new Error(String(t)),"parse_event"),t}}createMiddleware(){return async(e,t,r)=>{if(e.method!=="POST"){r&&r();return}if(e.body)try{await this.handleIncomingEvent(e.body),t.statusCode=202,t.end()}catch(s){t.statusCode=400,t.end(JSON.stringify({error:"Invalid event format"})),r&&r(s)}else{let s="";e.on("data",i=>s+=i.toString()),e.on("end",async()=>{try{await this.handleIncomingEvent(s),t.statusCode=202,t.end()}catch(i){t.statusCode=400,t.end(JSON.stringify({error:"Invalid event format"})),r&&r(i)}})}}}};var Ue=class{databaseDirectory;walFile;activeTable=null;data=new Map;writeBuffer=[];maxWriteOpsBeforeFlush=process.env.MAX_WRITE_OPS_BEFORE_FLUSH?Number.parseInt(process.env.MAX_WRITE_OPS_BEFORE_FLUSH):L().db.maxWriteOpsBeforeFlush;encryptionKey;cache;encryption;persistence;query;wal;mikroEvent;constructor(e,t){let{databaseDirectory:r,walFileName:s,walInterval:i}=e;this.databaseDirectory=r,this.walFile=re(this.databaseDirectory,s),this.encryptionKey=e.encryptionKey?e.encryptionKey:null,$e(this.databaseDirectory)||vt(this.databaseDirectory),this.cache=new Me,this.encryption=new W,this.persistence=new _,this.query=new Le,this.wal=new Ie(this.walFile,i),this.mikroEvent=new te,this.setupEvents(t)}async start(){await this.applyWALEntries()}setupEvents(e){e?.targets?.forEach(t=>{this.mikroEvent.addTarget({name:t.name,url:t.url,headers:t.headers,events:t.events})}),e?.listeners?.forEach(t=>this.mikroEvent.on(t.event,t.handler))}async setActiveTable(e){this.activeTable!==e&&(this.hasTable(e)||await this.loadTable(e),await this.applyWALEntries(e),await this.evictTablesIfNeeded(),this.activeTable=e)}async applyWALEntries(e){let t=await this.wal.loadWAL(e);if(t.length===0)return;let r=e?[e]:[...new Set(t.map(s=>s.tableName))];for(let s of r){let i=t.filter(o=>o.tableName===s);e&&!this.hasTable(s)?await this.loadTable(s):this.createTable(s);for(let o of i)o.operation==="W"&&o.data?this.setItem(s,o.key,o.data):o.operation==="D"&&await this.deleteItem(s,o.key)}}async loadTable(e){if(this.hasTable(e))return;let t=re(this.databaseDirectory,e);if(!$e(t)){this.createTable(e);return}let r=await Et(t),s=r;if(this.encryptionKey&&r.length>0&&r[0]===1)try{let o=this.encryption.deserialize(r),n=this.encryption.generateKey(this.encryptionKey,"salt"),l=this.encryption.decrypt(o,n);s=Buffer.from(l,"binary")}catch(o){console.error(`Failed to decrypt ${e}:`,o)}let i=this.persistence.readTableFromBinaryBuffer(s);this.data.set(e,i),this.data.size>this.cache.cacheLimit&&setImmediate(()=>this.evictTablesIfNeeded())}async get(e){let{tableName:t}=e,r=e.key,s=e.options;if(await this.setActiveTable(t),!s)return r?this.getItem(t,r)?.value:[...this.getAll(t)];let i=this.getTable(t),o=await this.query.query(i,s.filter,s.limit);if(s.sort&&(o=o.sort(s.sort)),s.offset!=null||s.limit!=null){let n=s.offset||0,l=s.limit?n+s.limit:void 0;o=o.slice(n,l)}return o}async write(e,t={}){let{concurrencyLimit:r=10,flushImmediately:s=!1}=t,i=Array.isArray(e)?e:[e],o=i.length,n=0;for(;n<o;){let l=i.slice(n,n+r),a=l.map(u=>this.writeItem(u,!1));if((await Promise.all(a)).includes(!1))return!1;n+=l.length,this.writeBuffer.length>=this.maxWriteOpsBeforeFlush&&await this.flushWrites()}return(s||o>=0)&&await this.flush(),!0}async writeItem(e,t=!1){let{tableName:r,key:s,value:i,expectedVersion:o=null,expiration:n=0}=e;await this.setActiveTable(r);let{success:l,newVersion:a}=this.getItemVersion(r,s,o);return l?(await this.wal.appendToWAL(r,"W",s,i,a,n),this.setItem(r,s,{value:i,v:a,t:v(),x:n}),this.addToWriteBuffer(r,s),t&&await this.flush(),!0):!1}async delete(e,t,r=null,s=!1){if(await this.setActiveTable(e),!this.hasTable(e)||!this.hasKey(e,t))return console.log(`Key ${t} not found in table ${e}`),!1;let{success:i,currentVersion:o,expiration:n}=this.getItemVersion(e,t,r);return i?(await this.wal.appendToWAL(e,"D",t,null,o,n),await this.deleteItem(e,t),s&&await this.flush(),!0):!1}getItemVersion(e,t,r){let s=this.getItem(e,t),i=s?s.version:0,o=i+1,n=s&&s.expiration||0,l=!0;return r!==null&&i!==r&&(console.log(`Version mismatch for ${e}:${t}. Expected ${r}, found ${i}`),l=!1),{success:l,currentRecord:s,currentVersion:i,newVersion:o,expiration:n}}createTable(e){this.trackTableAccess(e),this.hasTable(e)||this.data.set(e,new Map)}getTable(e){return this.trackTableAccess(e),this.hasTable(e)?this.data.get(e):new Map}async getTableSize(e){return await this.setActiveTable(e),this.trackTableAccess(e),this.data.get(e)?.size}async deleteTable(e){this.trackTableAccess(e),this.data.delete(e);let t="table.deleted",{success:r,errors:s}=await this.mikroEvent.emit(t,{operation:t,table:e});r||console.error("Error when emitting events:",s)}hasTable(e){return this.trackTableAccess(e),this.data.has(e)}hasKey(e,t){return this.trackTableAccess(e),this.data.get(e)?.has(t)}getItem(e,t){this.trackTableAccess(e);let r=this.data.get(e)?.get(t);if(r){if(r?.x!==0&&Date.now()>r?.x){this.deleteItem(e,t);return}return{value:r.value,version:r.v,timestamp:r.t,expiration:r.x}}}getAll(e){this.trackTableAccess(e);let t=this.data.get(e);return t?Array.from(t):[]}setItem(e,t,r){this.trackTableAccess(e),this.createTable(e),this.data.get(e)?.set(t,r)}async deleteItem(e,t){this.data.get(e)?.delete(t);let r="item.deleted",{success:s,errors:i}=await this.mikroEvent.emit(r,{operation:r,table:e,key:t});s||console.error("Error when emitting events:",i)}addToWriteBuffer(e,t){let r=this.getItem(e,t);this.writeBuffer.push(JSON.stringify({tableName:e,key:t,record:r}))}trackTableAccess(e){this.cache.trackTableAccess(e)}async flush(){await this.flushWAL(),await this.flushWrites()}async flushWAL(){await this.wal.flushWAL()}async flushWrites(){if(this.writeBuffer.length!==0)try{let e=new Map,t=[...this.writeBuffer];for(let s of t){let i=JSON.parse(s);e.has(i.tableName)||e.set(i.tableName,new Map),e.get(i.tableName).set(i.key,i.record);let n="item.written",{success:l,errors:a}=await this.mikroEvent.emit(n,{operation:n,table:i.tableName,key:i.key,record:i.record});l||console.error("Error when emitting events:",a)}let r=Array.from(e.entries()).map(async([s])=>{let i=this.getTable(s),o=re(this.databaseDirectory,s);await Ae(o,i,this.encryptionKey)});await Promise.all(r),this.writeBuffer=this.writeBuffer.slice(t.length)}catch(e){console.error(`Failed to flush writes: ${e.message}`)}}async flushTableToDisk(e){await this.setActiveTable(e);let t=this.getTable(e);if(t.size!==0){for(let[r,s]of t.entries())this.addToWriteBuffer(e,r);await this.flushWrites()}}async evictTablesIfNeeded(){let e=this.cache.findTablesForEviction(this.data.size);for(let t of e)await this.flushTableToDisk(t),this.data.delete(t)}async cleanupExpiredItems(){for(let[e,t]of this.data.entries()){let r=this.cache.findExpiredItems(t);for(let[s,i]of r){await this.wal.appendToWAL(e,"D",s,null,i.v,i.x),t.delete(s);let o="item.expired",{success:n,errors:l}=await this.mikroEvent.emit(o,{operation:o,table:e,key:s,record:i});n||console.error("Error when emitting events:",l)}}}async dump(e){e&&await this.setActiveTable(e);let t=this.getAll(this.activeTable);await Ct(`${this.databaseDirectory}/${this.activeTable}_dump.json`,JSON.stringify(t),"utf8")}getWAL(){return this.wal}getPersistence(){return this.persistence}};import{join as St}from"node:path";var j=class{table;checkpoint;constructor(e){let t=L(),r=e?.databaseDirectory||t.db.databaseDirectory,s=e?.walFileName||t.db.walFileName,i=e?.walInterval||t.db.walInterval,o=e?.encryptionKey||t.db.encryptionKey,n=e?.maxWriteOpsBeforeFlush||t.db.maxWriteOpsBeforeFlush,l=e?.events||{};e?.debug&&(process.env.DEBUG="true"),e?.maxWriteOpsBeforeFlush&&(process.env.MAX_WRITE_OPS_BEFORE_FLUSH=n.toString()),this.table=new Ue({databaseDirectory:r,walFileName:s,walInterval:i,encryptionKey:o},l);let a=this.table.getWAL();this.table.getWAL().setCheckpointCallback(()=>this.checkpoint.checkpoint(!0)),this.checkpoint=new ke({table:this.table,wal:a,walFile:St(r,s),checkpointIntervalMs:i}),this.checkpoint.start().catch(c=>console.error("Failed to start checkpoint service:",c))}async start(){await this.table.start(),await this.checkpoint.start()}async get(e){return await this.table.get(e)}async getTableSize(e){return await this.table.getTableSize(e)}async write(e,t){return await this.table.write(e,t)}async delete(e){let{tableName:t,key:r}=e,s=e?.expectedVersion||null;return await this.table.delete(t,r,s)}async deleteTable(e){return await this.table.deleteTable(e)}async close(){this.checkpoint&&this.checkpoint.stop(),this.table?.getWAL()&&this.table.getWAL().stop();try{await this.flush()}catch(e){console.error("Error flushing during close:",e)}}async flush(){await this.table.flush()}async flushWAL(){await this.table.flushWAL()}async dump(e){await this.table.dump(e)}async cleanupExpiredItems(){await this.table.cleanupExpiredItems()}};var Be=class{requests=new Map;limit;windowMs;constructor(e=100,t=60){this.limit=e,this.windowMs=t*1e3,setInterval(()=>this.cleanup(),this.windowMs)}getLimit(){return this.limit}isAllowed(e){let t=Date.now(),r=e||"unknown",s=this.requests.get(r);return(!s||s.resetTime<t)&&(s={count:0,resetTime:t+this.windowMs},this.requests.set(r,s)),s.count++,s.count<=this.limit}getRemainingRequests(e){let t=Date.now(),r=e||"unknown",s=this.requests.get(r);return!s||s.resetTime<t?this.limit:Math.max(0,this.limit-s.count)}getResetTime(e){let t=Date.now(),r=e||"unknown",s=this.requests.get(r);return!s||s.resetTime<t?Math.floor((t+this.windowMs)/1e3):Math.floor(s.resetTime/1e3)}cleanup(){let e=Date.now();for(let[t,r]of this.requests.entries())r.resetTime<e&&this.requests.delete(t)}};import{URL as kt}from"node:url";var Ne=class{routes=[];globalMiddlewares=[];pathPatterns=new Map;use(e){return this.globalMiddlewares.push(e),this}register(e,t,r,s=[]){return this.routes.push({method:e,path:t,handler:r,middlewares:s}),this.pathPatterns.set(t,this.createPathPattern(t)),this}get(e,...t){let r=t.pop();return this.register("GET",e,r,t)}post(e,...t){let r=t.pop();return this.register("POST",e,r,t)}put(e,...t){let r=t.pop();return this.register("PUT",e,r,t)}delete(e,...t){let r=t.pop();return this.register("DELETE",e,r,t)}patch(e,...t){let r=t.pop();return this.register("PATCH",e,r,t)}options(e,...t){let r=t.pop();return this.register("OPTIONS",e,r,t)}match(e,t){for(let r of this.routes){if(r.method!==e)continue;let s=this.pathPatterns.get(r.path);if(!s)continue;let i=s.pattern.exec(t);if(!i)continue;let o={};return s.paramNames.forEach((n,l)=>{o[n]=i[l+1]||""}),{route:r,params:o}}return null}createPathPattern(e){let t=[],r=e.replace(/\/:[^/]+/g,s=>{let i=s.slice(2);return t.push(i),"/([^/]+)"}).replace(/\/$/,"/?");return{pattern:new RegExp(`^${r}$`),paramNames:t}}async handle(e,t){let r=e.method||"GET",s=new kt(e.url||"/",`http://${e.headers.host}`),i=s.pathname,o=this.match(r,i);if(!o)return null;let{route:n,params:l}=o,a={};s.searchParams.forEach((h,d)=>{a[d]=h});let c={req:e,res:t,params:l,query:a,body:e.body||{},headers:e.headers,path:i,state:{},raw:()=>t,binary:(h,d="application/octet-stream",f=200)=>({statusCode:f,body:h,headers:{"Content-Type":d,"Content-Length":h.length.toString()},isRaw:!0}),text:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"text/plain"}}),form:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"application/x-www-form-urlencoded"}}),json:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"application/json"}}),html:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"text/html"}}),redirect:(h,d=302)=>({statusCode:d,body:null,headers:{Location:h}}),status:function(h){return{raw:()=>t,binary:(d,f="application/octet-stream")=>({statusCode:h,body:d,headers:{"Content-Type":f,"Content-Length":d.length.toString()},isRaw:!0}),text:d=>({statusCode:h,body:d,headers:{"Content-Type":"text/plain"}}),json:d=>({statusCode:h,body:d,headers:{"Content-Type":"application/json"}}),html:d=>({statusCode:h,body:d,headers:{"Content-Type":"text/html"}}),form:d=>({statusCode:h,body:d,headers:{"Content-Type":"application/x-www-form-urlencoded"}}),redirect:(d,f=302)=>({statusCode:f,body:null,headers:{Location:d}}),status:d=>this.status(d)}}},u=[...this.globalMiddlewares,...n.middlewares];return this.executeMiddlewareChain(c,u,n.handler)}async executeMiddlewareChain(e,t,r){let s=0,i=async()=>{if(s<t.length){let o=t[s++];return o(e,i)}return r(e)};return i()}};var z=()=>({port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",debug:Tt(process.env.DEBUG)||!1,rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"]});function Tt(e){return e==="true"||e===!0}var C=z(),Pe=e=>({configFilePath:"mikroserve.config.json",args:process.argv,options:[{flag:"--port",path:"port",defaultValue:C.port},{flag:"--host",path:"host",defaultValue:C.host},{flag:"--https",path:"useHttps",defaultValue:C.useHttps,isFlag:!0},{flag:"--http2",path:"useHttp2",defaultValue:C.useHttp2,isFlag:!0},{flag:"--cert",path:"sslCert",defaultValue:C.sslCert},{flag:"--key",path:"sslKey",defaultValue:C.sslKey},{flag:"--ca",path:"sslCa",defaultValue:C.sslCa},{flag:"--ratelimit",path:"rateLimit.enabled",defaultValue:C.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"rateLimit.requestsPerMinute",defaultValue:C.rateLimit.requestsPerMinute},{flag:"--allowed",path:"allowedDomains",defaultValue:C.allowedDomains,parser:T.array},{flag:"--debug",path:"debug",defaultValue:C.debug,isFlag:!0}],config:e});import{readFileSync as $}from"node:fs";import se from"node:http";import xt from"node:http2";import It from"node:https";var V=class{config;rateLimiter;router;constructor(e){let t=new k(Pe(e||{})).get();t.debug&&console.log("Using configuration:",t),this.config=t,this.router=new Ne;let r=t.rateLimit.requestsPerMinute||z().rateLimit.requestsPerMinute;this.rateLimiter=new Be(r,60),t.rateLimit.enabled===!0&&this.use(this.rateLimitMiddleware.bind(this))}use(e){return this.router.use(e),this}get(e,...t){return this.router.get(e,...t),this}post(e,...t){return this.router.post(e,...t),this}put(e,...t){return this.router.put(e,...t),this}delete(e,...t){return this.router.delete(e,...t),this}patch(e,...t){return this.router.patch(e,...t),this}options(e,...t){return this.router.options(e,...t),this}start(){let e=this.createServer(),{port:t,host:r}=this.config;return this.setupGracefulShutdown(e),e.listen(t,r,()=>{let s=e.address(),i=this.config.useHttps||this.config.useHttp2?"https":"http";console.log(`MikroServe running at ${i}://${s.address!=="::"?s.address:"localhost"}:${s.port}`)}),e}createServer(){let e=this.requestHandler.bind(this);if(this.config.useHttp2){if(!this.config.sslCert||!this.config.sslKey)throw new Error("SSL certificate and key paths are required when useHttp2 is true");try{let t={key:$(this.config.sslKey),cert:$(this.config.sslCert),...this.config.sslCa?{ca:$(this.config.sslCa)}:{}};return xt.createSecureServer(t,e)}catch(t){throw t.message.includes("key values mismatch")?new Error(`SSL certificate and key do not match: ${t.message}`):t}}else if(this.config.useHttps){if(!this.config.sslCert||!this.config.sslKey)throw new Error("SSL certificate and key paths are required when useHttps is true");try{let t={key:$(this.config.sslKey),cert:$(this.config.sslCert),...this.config.sslCa?{ca:$(this.config.sslCa)}:{}};return It.createServer(t,e)}catch(t){throw t.message.includes("key values mismatch")?new Error(`SSL certificate and key do not match: ${t.message}`):t}}return se.createServer(e)}async rateLimitMiddleware(e,t){let r=e.req.socket.remoteAddress||"unknown";return e.res.setHeader("X-RateLimit-Limit",this.rateLimiter.getLimit().toString()),e.res.setHeader("X-RateLimit-Remaining",this.rateLimiter.getRemainingRequests(r).toString()),e.res.setHeader("X-RateLimit-Reset",this.rateLimiter.getResetTime(r).toString()),this.rateLimiter.isAllowed(r)?t():{statusCode:429,body:{error:"Too Many Requests",message:"Rate limit exceeded, please try again later"},headers:{"Content-Type":"application/json"}}}async requestHandler(e,t){let r=Date.now(),s=e.method||"UNKNOWN",i=e.url||"/unknown",o=this.config.debug;try{if(this.setCorsHeaders(t,e),this.setSecurityHeaders(t,this.config.useHttps),o&&console.log(`${s} ${i}`),e.method==="OPTIONS"){if(t instanceof se.ServerResponse)t.statusCode=204,t.end();else{let l=t;l.writeHead(204),l.end()}return}try{e.body=await this.parseBody(e)}catch(l){return o&&console.error("Body parsing error:",l.message),this.respond(t,{statusCode:400,body:{error:"Bad Request",message:l.message}})}let n=await this.router.handle(e,t);return n?n._handled?void 0:this.respond(t,n):this.respond(t,{statusCode:404,body:{error:"Not Found",message:"The requested endpoint does not exist"}})}catch(n){return console.error("Server error:",n),this.respond(t,{statusCode:500,body:{error:"Internal Server Error",message:o?n.message:"An unexpected error occurred"}})}finally{o&&this.logDuration(r,s,i)}}logDuration(e,t,r){let s=Date.now()-e;console.log(`${t} ${r} completed in ${s}ms`)}async parseBody(e){return new Promise((t,r)=>{let s=[],i=0,o=1024*1024,n=!1,l=this.config.debug,a=e.headers["content-type"]||"";l&&console.log("Content-Type:",a),e.on("data",c=>{if(i+=c.length,l&&console.log(`Received chunk: ${c.length} bytes, total size: ${i}`),i>o&&!n){n=!0,l&&console.log(`Body size exceeded limit: ${i} > ${o}`),r(new Error("Request body too large"));return}n||s.push(c)}),e.on("end",()=>{if(!n){l&&console.log(`Request body complete: ${i} bytes`);try{if(s.length>0){let c=Buffer.concat(s).toString("utf8");if(a.includes("application/json"))try{t(JSON.parse(c))}catch(u){r(new Error(`Invalid JSON in request body: ${u.message}`))}else if(a.includes("application/x-www-form-urlencoded")){let u={};new URLSearchParams(c).forEach((h,d)=>{u[d]=h}),t(u)}else t(c)}else t({})}catch(c){r(new Error(`Invalid request body: ${c}`))}}}),e.on("error",c=>{n||r(new Error(`Error reading request body: ${c.message}`))})})}setCorsHeaders(e,t){let r=t.headers.origin,{allowedDomains:s=["*"]}=this.config;!r||s.length===0||s.includes("*")?e.setHeader("Access-Control-Allow-Origin","*"):s.includes(r)&&(e.setHeader("Access-Control-Allow-Origin",r),e.setHeader("Vary","Origin")),e.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, PATCH, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization"),e.setHeader("Access-Control-Max-Age","86400")}setSecurityHeaders(e,t=!1){let r={"X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Content-Security-Policy":"default-src 'self'; script-src 'self'; object-src 'none'","X-XSS-Protection":"1; mode=block"};if((t||this.config.useHttp2)&&(r["Strict-Transport-Security"]="max-age=31536000; includeSubDomains"),e instanceof se.ServerResponse)Object.entries(r).forEach(([s,i])=>{e.setHeader(s,i)});else{let s=e;Object.entries(r).forEach(([i,o])=>{s.setHeader(i,o)})}}respond(e,t){let r={...t.headers||{}};(i=>typeof i.writeHead=="function"&&typeof i.end=="function")(e)?(e.writeHead(t.statusCode,r),t.body===null||t.body===void 0?e.end():t.isRaw||typeof t.body=="string"?e.end(t.body):e.end(JSON.stringify(t.body))):(console.warn("Unexpected response object type without writeHead/end methods"),e.writeHead?.(t.statusCode,r),t.body===null||t.body===void 0?e.end?.():t.isRaw||typeof t.body=="string"?e.end?.(t.body):e.end?.(JSON.stringify(t.body)))}setupGracefulShutdown(e){let t=r=>{console.log("Shutting down MikroServe server..."),r&&console.error("Error:",r),e.close(()=>{console.log("Server closed successfully"),setImmediate(()=>process.exit(r?1:0))})};process.on("SIGINT",()=>t()),process.on("SIGTERM",()=>t()),process.on("uncaughtException",t),process.on("unhandledRejection",t)}};async function De(e){let t=new j({...e.db,events:e?.events});await t.start();let r=new V(e.server);return r.get("/table",async s=>{let i=s.req.body;if(!i.tableName)return s.json({statusCode:400,body:"tableName is required"});let o=await t.getTableSize(i.tableName);return o||s.json({statusCode:404,body:null}),s.json({statusCode:200,body:o})}),r.post("/get",async s=>{let i=s.req.body;if(!i.tableName)return s.json({statusCode:400,body:"tableName is required"});let o={tableName:i.tableName,key:i.key},n=Ee(i?.options);n&&(o.options=n);let l=await t.get(o);return l||s.json({statusCode:404,body:null}),s.json({statusCode:200,body:l})}),r.post("/write",async s=>{let i=s.req.body;if(!i.tableName||i.value===void 0)return s.json({statusCode:400,body:"tableName and value are required"});let o={tableName:i.tableName,key:i.key,value:i.value,expectedVersion:i.expectedVersion,expiration:i.expiration},n={concurrencyLimit:i.concurrencyLimit,flushImmediately:i.flushImmediately},l=await t.write(o,n);return s.json({statusCode:200,body:l})}),r.delete("/delete",async s=>{let i=s.params;if(!i.tableName||!i.key)return s.json({statusCode:400,body:"tableName and key are required"});let o={tableName:i.tableName,key:i.key},n=await t.delete(o);return s.json({statusCode:200,body:n})}),r.start(),r}async function At(){let t=process.argv[1]?.includes("node_modules/.bin/mikrodb"),r=(process.argv[2]||"")==="--force";if(t||r){console.log("\u{1F5C2}\uFE0F Welcome to MikroDB! \u2728");try{let s=L(),i=new k({configFilePath:"mikrodb.config.json",args:process.argv,options:[{flag:"--db",path:"db.dbName",defaultValue:s.db.dbName},{flag:"--dir",path:"db.databaseDirectory",defaultValue:s.db.databaseDirectory},{flag:"--wal",path:"db.walFileName",defaultValue:s.db.walFileName},{flag:"--interval",path:"db.walInterval",defaultValue:s.db.walInterval},{flag:"--encryptionKey",path:"db.encryptionKey",defaultValue:s.db.encryptionKey},{flag:"--maxWrites",path:"db.maxWriteOpsBeforeFlush",defaultValue:s.db.maxWriteOpsBeforeFlush},{flag:"--debug",path:"db.debug",isFlag:!0,defaultValue:s.db.debug},{path:"events",defaultValue:s.events},{flag:"--port",path:"server.port",defaultValue:s.server.port},{flag:"--host",path:"server.host",defaultValue:s.server.host},{flag:"--https",path:"server.useHttps",isFlag:!0,defaultValue:s.server.useHttps},{flag:"--http2",path:"server.useHttp2",isFlag:!0,defaultValue:s.server.useHttp2},{flag:"--cert",path:"server.sslCert",defaultValue:s.server.sslCert},{flag:"--key",path:"server.sslKey",defaultValue:s.server.sslKey},{flag:"--ca",path:"server.sslCa",defaultValue:s.server.sslCa},{flag:"--debug",path:"server.debug",isFlag:!0,defaultValue:s.server.debug}]}).get();De(i)}catch(s){console.error(s)}}}At();import{EventEmitter as $t}from"node:events";import{getRandomValues as Mt}from"node:crypto";var R=class{options;defaultLength=16;defaultOnlyLowerCase=!1;defaultStyle="extended";defaultUrlSafe=!0;constructor(e){if(this.options={},e)for(let[t,r]of Object.entries(e))this.options[t]=this.generateConfig(r)}add(e){if(!e?.name)throw new Error("Missing name for the ID configuration");let t=this.generateConfig(e);this.options[e.name]=t}remove(e){if(!e?.name)throw new Error("Missing name for the ID configuration");delete this.options[e.name]}create(e,t,r,s){let i=this.generateConfig({length:e,style:t,onlyLowerCase:r,urlSafe:s});return this.generateId(i)}custom(e){if(this.options[e])return this.generateId(this.options[e]);throw new Error(`No configuration found with name: ${e}`)}generateConfig(e){return{name:e?.name||"",length:e?.length||this.defaultLength,onlyLowerCase:e?.onlyLowerCase??this.defaultOnlyLowerCase,style:e?.style||this.defaultStyle,urlSafe:e?.urlSafe??this.defaultUrlSafe}}getCharacterSet(e,t,r){if(e==="hex")return t?"0123456789abcdef":"0123456789ABCDEFabcdef";if(e==="alphanumeric")return t?"abcdefghijklmnopqrstuvwxyz0123456789":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";if(e==="extended")return r?t?"abcdefghijklmnopqrstuvwxyz0123456789-._~":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~":t?"abcdefghijklmnopqrstuvwxyz0123456789-._~!$()*+,;=:":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$()*+,;=:";throw new Error(`Unknown ID style "${e} provided. Must be one of "extended" (default), "alphanumeric", or "hex".`)}generateId(e){let{length:t,onlyLowerCase:r,style:s,urlSafe:i}=e;if(t<0||t===0)throw new Error("ID length cannot be negative");let o=this.getCharacterSet(s,r,i),n=(2<<Math.log(o.length-1)/Math.LN2)-1,l=Math.ceil(1.6*n*t/o.length),a="";for(;a.length<t;){let c=new Uint8Array(l);Mt(c);for(let u=0;u<l;u++){let h=c[u]&n;if(h<o.length&&(a+=o[h],a.length===t))break}}return a}};var U=class{async getUserById(t){return this.db.get(`user:${t}`)}async getUserByUsername(t){return(await this.db.list("user:")).find(s=>s.userName===t)||null}async createUser(t){await this.db.set(`user:${t.id}`,t)}async deleteUser(t){await this.db.delete(`user:${t}`)}async listUsers(){return this.db.list("user:")}async getUserByEmail(t){return(await this.db.list("user:")).find(s=>s.email===t)||null}async getChannelById(t){return this.db.get(`channel:${t}`)}async getChannelByName(t){return(await this.db.list("channel:")).find(s=>s.name===t)||null}async createChannel(t){await this.db.set(`channel:${t.id}`,t)}async updateChannel(t){await this.db.set(`channel:${t.id}`,t)}async deleteChannel(t){await this.db.delete(`channel:${t}`)}async listChannels(){return this.db.list("channel:")}async getMessageById(t){return await this.db.get(`message:${t}`)}async listMessagesByChannel(t){return(await this.db.list("message:")).filter(s=>s.channelId===t).sort((s,i)=>s.createdAt-i.createdAt)}async createMessage(t){await this.db.set(`message:${t.id}`,t)}async updateMessage(t){await this.db.set(`message:${t.id}`,t)}async deleteMessage(t){await this.db.delete(`message:${t}`)}async addReaction(t,r,s){let i=await this.getMessageById(t);if(!i)return null;i.reactions||(i.reactions={});let o=i.reactions[r]||[];return o.includes(s)||(i.reactions[r]=[...o,s],await this.updateMessage(i)),i}async removeReaction(t,r,s){let i=await this.getMessageById(t);return i?(!i.reactions||!i.reactions[r]||(i.reactions[r]=i.reactions[r].filter(o=>o!==s),await this.updateMessage(i)),i):null}async getServerSettings(){return this.db.get("server:settings")}async updateServerSettings(t){await this.db.set("server:settings",t)}};var q=class extends U{db;constructor(){super(),this.db=new ie}},ie=class{data={};async get(t){return this.data[t]||null}async set(t,r){this.data[t]=r}async delete(t){delete this.data[t]}async list(t){let r=[];for(let s in this.data)s.startsWith(t)&&r.push(this.data[s]);return r}};var x="mikrochat_id",ae={name:x,length:12,urlSafe:!0},je=()=>{let e=Lt(process.env.DEBUG)||!1;return{devMode:!1,auth:{jwtSecret:process.env.AUTH_JWT_SECRET||"your-jwt-secret",magicLinkExpirySeconds:15*60,jwtExpirySeconds:15*60,refreshTokenExpirySeconds:7*24*60*60,maxActiveSessions:3,appUrl:process.env.APP_URL||"http://127.0.0.1:3000",isInviteRequired:!0,debug:e},email:{emailSubject:"Your Secure Login Link",user:process.env.EMAIL_USER||"",host:process.env.EMAIL_HOST||"",password:process.env.EMAIL_PASSWORD||"",port:465,secure:!0,maxRetries:2,debug:e},storage:{databaseDirectory:"mikrochat_db",encryptionKey:process.env.STORAGE_KEY||"",debug:e},server:{port:Number(process.env.PORT)||3e3,host:process.env.HOST||"localhost",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["http://127.0.0.1:8080"],debug:e},chat:{initialUser:{id:process.env.INITIAL_USER_ID||new R().add(ae),userName:process.env.INITIAL_USER_NAME||"",email:process.env.INITIAL_USER_EMAIL||""},messageRetentionDays:30,maxMessagesPerChannel:100}}};function Lt(e){return e==="true"||e===!0}var K=class{config;db;id;eventEmitter;generalChannelName="General";constructor(t,r){this.config=t,this.db=r||new q,this.id=new R,this.eventEmitter=new $t,this.eventEmitter.setMaxListeners(0),this.initialize()}async initialize(){let t=this.config.initialUser.id,r=this.config.initialUser.userName,s=this.config.initialUser.email;if(!r||!s)throw new Error('Missing required data to start a new MikroChat server. Required arguments are "initialUser.userName" and "initialUser.email".');this.id.add(ae),await this.getUserByEmail(s)||await this.createUser({id:t||this.id.custom(x),userName:r||s.split("@")[0],email:s,isAdmin:!0,createdAt:Date.now()}),await this.db.getChannelByName(this.generalChannelName)||await this.db.createChannel({id:this.id.custom(x),name:this.generalChannelName,createdAt:Date.now(),createdBy:this.config.initialUser.id}),this.scheduleMessageCleanup()}scheduleMessageCleanup(){setInterval(async()=>{let s=await this.db.listChannels(),i=24*60*60*1e3,o=Date.now()-this.config.messageRetentionDays*i;for(let n of s){let l=await this.db.listMessagesByChannel(n.id);for(let a of l)a.createdAt<o&&(await this.db.deleteMessage(a.id),this.emitEvent({type:"DELETE_MESSAGE",payload:{id:a.id,channelId:n.id}}))}},36e5)}async getServerSettings(){return await this.db.getServerSettings()}async updateServerSettings(t){return await this.db.updateServerSettings(t)}async getMessageById(t){return await this.db.getMessageById(t)}async listUsers(){return await this.db.listUsers()}async getUserById(t){return await this.db.getUserById(t)}async getUserByEmail(t){return await this.db.getUserByEmail(t)}async createUser(t){return await this.db.createUser(t)}async deleteUser(t){await this.db.deleteUser(t)}async addUser(t,r="",s=!1,i=!1){let o=await this.getUserById(r);if(!i&&!o)throw new Error("User not found");if(s&&!o?.isAdmin)throw new Error("Only administrators can add admin users");if(await this.getUserByEmail(t))throw new Error("User with this email already exists");let l=this.id.custom(x),a={id:l,userName:t.split("@")[0],email:t,isAdmin:s,createdAt:Date.now(),addedBy:r||l};return await this.createUser(a),this.emitEvent({type:"NEW_USER",payload:{id:a.id,userName:a.userName,email:a.email}}),a}async removeUser(t,r){let s=await this.getUserById(t);if(!s)throw new Error("User not found");let i=await this.getUserById(r);if(!i)throw new Error("Requester not found");if(!i.isAdmin)throw new Error("Only administrators can remove users");if(s.isAdmin&&(await this.listUsers()).filter(n=>n.isAdmin).length<=1)throw new Error("Cannot remove the last administrator");await this.deleteUser(t),this.emitEvent({type:"REMOVE_USER",payload:{id:t,email:s.email}})}async exitUser(t){let r=await this.getUserById(t);if(!r)throw new Error("User not found");await this.deleteUser(t),this.emitEvent({type:"USER_EXIT",payload:{id:t,userName:r.userName}})}async createChannel(t,r){if(await this.db.getChannelByName(t))throw new Error(`Channel with name "${t}" already exists`);let i={id:this.id.custom(x),name:t,createdAt:Date.now(),createdBy:r};return await this.db.createChannel(i),this.emitEvent({type:"NEW_CHANNEL",payload:i}),i}async updateChannel(t,r,s){let i=await this.db.getChannelById(t);if(!i)throw new Error("Channel not found");let o=await this.getUserById(s);if(!o)throw new Error("User not found");if(i.createdBy!==s&&!o.isAdmin)throw new Error("You can only edit channels you created");let n=await this.db.getChannelById(t);if(n&&n?.name===this.generalChannelName)throw new Error(`The ${this.generalChannelName} channel cannot be renamed`);if(n&&n?.id!==t)throw new Error(`Channel with name "${r}" already exists`);return i.name=r,i.updatedAt=Date.now(),await this.db.updateChannel(i),this.emitEvent({type:"UPDATE_CHANNEL",payload:i}),i}async deleteChannel(t,r){let s=await this.db.getChannelById(t);if(!s)throw new Error("Channel not found");let i=await this.getUserById(r);if(!i)throw new Error("User not found");if(s.createdBy!==r&&!i.isAdmin)throw new Error("You can only delete channels you created");if(s.name.toLowerCase()===this.generalChannelName.toLowerCase())throw new Error("The General channel cannot be deleted");let o=await this.db.listMessagesByChannel(t);for(let n of o)await this.db.deleteMessage(n.id);await this.db.deleteChannel(t),this.emitEvent({type:"DELETE_CHANNEL",payload:{id:t,name:s.name}})}async listChannels(){return await this.db.listChannels()}async createMessage(t,r,s,i=[]){let o=await this.getUserById(r);if(!o)throw new Error("Author not found");if(!await this.db.getChannelById(s))throw new Error("Channel not found");let l=Date.now(),a={id:this.id.custom(x),author:{id:r,userName:o.userName},images:i,content:t,channelId:s,createdAt:l,updatedAt:l,reactions:{}};await this.db.createMessage(a);let c=await this.db.listMessagesByChannel(s);if(c.length>this.config.maxMessagesPerChannel){let u=c[0];await this.db.deleteMessage(u.id),this.emitEvent({type:"DELETE_MESSAGE",payload:{id:u.id,channelId:s}})}return this.emitEvent({type:"NEW_MESSAGE",payload:a}),a}async updateMessage(t,r,s,i){let o=await this.getMessageById(t);if(!o)throw new Error("Message not found");if(o.author.id!==r)throw new Error("You can only edit your own messages");let n=[];return s&&(o.content=s),i&&(n=(o.images||[]).filter(a=>!i.includes(a)),o.images=i),o.updatedAt=Date.now(),await this.db.updateMessage(o),this.emitEvent({type:"UPDATE_MESSAGE",payload:o}),{message:o,removedImages:n}}async deleteMessage(t,r){let s=await this.getMessageById(t);if(!s)throw new Error("Message not found");let i=await this.getUserById(r);if(!i)throw new Error("User not found");if(s.author.id!==r&&!i.isAdmin)throw new Error("You can only delete your own messages");await this.db.deleteMessage(t),this.emitEvent({type:"DELETE_MESSAGE",payload:{id:t,channelId:s.channelId}})}async getMessagesByChannel(t){return await this.db.listMessagesByChannel(t)}async addReaction(t,r,s){if(!await this.getUserById(r))throw new Error("User not found");if(!await this.getMessageById(t))throw new Error("Message not found");let n=await this.db.addReaction(t,r,s);if(!n)throw new Error("Failed to add reaction");return this.emitEvent({type:"NEW_REACTION",payload:{messageId:t,userId:r,reaction:s}}),n}async removeReaction(t,r,s){if(!await this.getUserById(r))throw new Error("User not found");if(!await this.getMessageById(t))throw new Error("Message not found");let n=await this.db.removeReaction(t,r,s);if(!n)throw new Error("Failed to remove reaction");return this.emitEvent({type:"DELETE_REACTION",payload:{messageId:t,userId:r,reaction:s}}),n}emitEvent(t){console.log(`Emitting event ${t.type}`,t.payload),this.eventEmitter.emit("sse",t)}subscribeToEvents(t){let r=s=>t(s);return this.eventEmitter.on("sse",r),()=>this.eventEmitter.off("sse",r)}};import{randomBytes as Ve}from"node:crypto";import{existsSync as Re,mkdirSync as Ut,readFileSync as Bt,unlinkSync as Nt,writeFileSync as Pt}from"node:fs";import{join as ne}from"node:path";var Dt=2,jt=["jpg","jpeg","png","webp","svg"],Vt=3,Rt=60*1e3,B=new Map,I=new Map;async function He(e){let{config:t,auth:r,chat:s,devMode:i,isInviteRequired:o}=e,n=new V(t);console.log("Dev mode?",i),i&&n.post("/auth/dev-login",async a=>{if(!a.body.email)return a.json({error:"Email is required"},400);let{email:c}=a.body,u;if(o){if(u=await s.getUserByEmail(c),!u)return a.json({success:!1,message:"Unauthorized"},401)}else u=await s.addUser(c,c,!1,!0);let h=r.generateJsonWebToken({id:u.id,username:u.userName,email:u.email,role:u.isAdmin?"admin":"user"});return a.json({user:u,token:h},200)}),n.post("/auth/login",async a=>{if(!a.body.email)return a.json({error:"Email is required"},400);let{email:c}=a.body,u="If a matching account was found, a magic link has been sent.";async function h(){let d=await r.createMagicLink({email:c});if(!d)return a.json({error:"Failed to create magic link"},400);u=d.message}return o?await s.getUserByEmail(c)&&await h():await h(),a.json({success:!0,message:u},200)}),n.post("/auth/logout",l,async a=>{let u=a.body.refreshToken;if(!u)return a.json({error:"Missing refresh token"},400);let h=await r.logout(u);return a.json(h,200)}),n.get("/auth/me",l,async a=>a.json({user:a.state.user},200)),n.post("/auth/verify",async a=>{let c=a.body,h=(a.headers.authorization||"").split(" ")[1];if(!h)return a.json({error:"Token is required"},400);let d;try{if(d=await r.verifyToken({email:c.email,token:h}),!d)return a.json({error:"Invalid token"},400)}catch{return a.json({error:"Invalid token"},400)}return a.json(d,200)}),n.post("/auth/refresh",async a=>{let u=a.body.refreshToken,h=await r.refreshAccessToken(u);return a.json(h,200)}),n.get("/auth/sessions",l,async a=>{let c=a.headers.authorization;if(!c||!c.startsWith("Bearer "))return a.json(null,401);let u=a.body,h=c.split(" ")[1],f={email:r.verify(h).sub},y=await r.getSessions({body:u,user:f});return a.json(y,200)}),n.delete("/auth/sessions",l,async a=>{let c=a.headers.authorization;if(!c||!c.startsWith("Bearer "))return a.json(null,401);let u=a.body,h=c.split(" ")[1],f={email:r.verify(h).sub},y=await r.revokeSessions({body:u,user:f});return a.json(y,200)}),n.post("/users/add",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let{email:u,role:h}=a.body;if(!u)return a.json({error:"Email is required"},400);try{if(h==="admin"&&!c.isAdmin)return a.json({error:"Only administrators can add admin users"},403);if(await s.getUserByEmail(u))return a.json({success:!1,message:"User already exists"});let f=await s.addUser(u,c.id,h==="admin");return a.json({success:!0,userId:f},200)}catch(d){return a.json({error:d.message},400)}}),n.get("/users",l,async a=>{if(!a.state.user)return a.json({error:"Unauthorized"},401);try{let u=await s.listUsers();return a.json({users:u},200)}catch(u){return a.json({error:u.message},400)}}),n.delete("/users/:id",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.id;if(u===c.id)return a.json({error:"You cannot remove your own account"},400);try{return await s.removeUser(u,c.id),a.json({success:!0},200)}catch(h){return a.json({error:h.message},400)}}),n.post("/users/exit",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);try{return c.isAdmin&&(await s.listUsers()).filter(h=>h.isAdmin).length<=1?a.json({error:"Cannot exit as the last administrator"},400):(await s.exitUser(c.id),a.json({success:!0,message:"You have exited the server"},200))}catch(u){return a.json({error:u.message},400)}}),n.get("/channels",l,async a=>{if(!a.state.user)return a.json({error:"Unauthorized"},401);let u=await s.listChannels();return a.json({channels:u},200)}),n.post("/channels",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let{name:u}=a.body;if(!u)return a.json({error:"Channel name is required"},400);try{let h=await s.createChannel(u,c.id);return a.json({channel:h},200)}catch(h){return a.json({error:h.message},400)}}),n.get("/channels/:channelId/messages",l,async a=>{if(!a.state.user)return a.json({error:"Unauthorized"},401);let u=a.params.channelId;try{let h=await s.getMessagesByChannel(u),d=await Promise.all(h.map(async f=>{let y=await s.getUserById(f.author.id);return{...f,author:{id:y?.id,userName:y?.userName||"Unknown User"}}}));return a.json({messages:d},200)}catch(h){return a.json({error:h.message},400)}}),n.post("/channels/:channelId/messages",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.channelId,h=a.body?.content,d=a.body?.images;if(!h&&!d)return a.json({error:"Message content is required"},400);try{let f=await s.createMessage(h,c.id,u);return a.json({message:f},200)}catch(f){return a.json({error:f.message},400)}}),n.put("/channels/:channelId",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.channelId,{name:h}=a.body;if(!h)return a.json({error:"Channel name is required"},400);try{let d=await s.updateChannel(u,h,c.id);return a.json({channel:d},200)}catch(d){return a.json({error:d.message},400)}}),n.delete("/channels/:channelId",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.channelId;try{return await s.deleteChannel(u,c.id),a.json({success:!0},200)}catch(h){return a.json({error:h.message},400)}}),n.put("/messages/:messageId",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.messageId,h=a.body?.content,d=a.body?.images;if(!h&&!d)return a.json({error:"Message content is required"},400);try{let{message:f,removedImages:y}=await s.updateMessage(u,c.id,h,d);return Fe(y),a.json({message:f},200)}catch(f){return a.json({error:f.message},400)}}),n.delete("/messages/:messageId",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.messageId;try{let d=(await s.getMessageById(u))?.images||[];return await s.deleteMessage(u,c.id),Fe(d),a.json({success:!0},200)}catch(h){return a.json({error:h.message},400)}}),n.post("/messages/:messageId/reactions",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.messageId,{reaction:h}=a.body;if(!h)return a.json({error:"Reaction is required"},400);try{let d=await s.addReaction(u,c.id,h);return d?a.json({message:d},200):a.json({error:`Message with ID ${u} not found`},401)}catch(d){return console.error(`Error adding reaction to message ${u}:`,d),a.json({error:d.message},400)}}),n.delete("/messages/:messageId/reactions",l,async a=>{let c=a.state.user;if(!c)return a.json({error:"Unauthorized"},401);let u=a.params.messageId,{reaction:h}=a.body;if(!h)return a.json({error:"Reaction is required"},400);try{let d=await s.removeReaction(u,c.id,h);return d?a.json({message:d},200):a.json({error:`Message with ID ${u} not found`},404)}catch(d){return console.error(`Error removing reaction from message ${u}:`,d),a.json({error:d.message},400)}}),n.post("/channels/:channelId/messages/image",l,async a=>{if(!a.state.user)return a.json({error:"Unauthorized"},401);try{let{filename:u,image:h}=a.body;if(!h)return a.json({error:"No image provided"},400);let d=u.split(".").pop();if(!d)return a.json({error:"Missing file extension"},400);if(!jt.includes(d))return a.json({error:"Unsupported file format"},400);let f=Buffer.from(h,"base64"),y=Dt*1024*1024;if(f.length>y)return a.json({error:"Image too large"},400);let b=`${process.cwd()}/uploads`;Re(b)||Ut(b);let S=`${Date.now()}-${Ve(1).toString("hex")}.${d}`,p=ne(b,S);return Pt(p,f),a.json({success:!0,filename:S})}catch(u){return console.error("Image upload error:",u),a.json({success:!1,error:u instanceof Error?u.message:"Upload failed"},500)}}),n.get("/channels/:channelId/messages/image/:filename",l,async a=>{if(!a.state.user)return a.json({error:"Unauthorized"},401);try{let{filename:u}=a.params,h=`${process.cwd()}/uploads`,d=ne(h,u);if(!Re(d))return a.json({error:"Image not found"},404);let f=Bt(d),y=u.split(".").pop()?.toLowerCase(),b="application/octet-stream";return y==="jpg"||y==="jpeg"?b="image/jpeg":y==="png"?b="image/png":y==="webp"?b="image/webp":y==="svg"&&(b="image/svg+xml"),a.binary(f,b)}catch(u){return a.json({success:!1,error:u instanceof Error?u.message:"Image fetch failed"},500)}}),n.get("/server/settings",l,async a=>{if(!a.state.user)return a.json({error:"Unauthorized"},401);try{let u=await s.getServerSettings();return a.json(u||{name:"MikroChat"},200)}catch(u){return a.json({error:u.message},400)}}),n.put("/server/settings",l,async a=>{if(!a.state.user)return a.json({error:"Unauthorized"},401);let{name:u}=a.body;if(!u)return a.json({error:"Server name is required"},400);try{return await s.updateServerSettings({name:u}),a.json({name:u},200)}catch(h){return a.json({error:h.message},400)}}),n.get("/events",async a=>{let c=null,u=a.query.token;if(u)try{let p=r.verify(u);c=await s.getUserByEmail(p.email||p.sub)}catch(p){console.error("SSE token validation error:",p)}if(!c&&a.headers.authorization?.startsWith("Bearer ")){let p=a.headers.authorization.substring(7);try{let w=r.verify(p);c=await s.getUserByEmail(w.email||w.sub)}catch(w){console.error("SSE header validation error:",w)}}if(!c)return console.log("SSE unauthorized access attempt"),{statusCode:401,body:{error:"Unauthorized"},headers:{"Content-Type":"application/json"}};let h=Ve(8).toString("hex");if(B.has(c.id)){let p=Date.now(),w=B.get(c.id),le=[];if(w.forEach(E=>{let F=I.get(E)||0;p-F>Rt&&le.push(E)}),le.forEach(E=>{w.delete(E),I.delete(E),console.log(`Cleaned up stale connection ${E} for user ${c.id}`)}),w.size>=Vt){let E=null,F=Number.POSITIVE_INFINITY;w.forEach(ce=>{let ue=I.get(ce)||0;ue<F&&(F=ue,E=ce)}),E&&(w.delete(E),I.delete(E),console.log(`Removed oldest connection ${E} for user ${c.id} to make room`))}}else B.set(c.id,new Set);let d=B.get(c.id);d.add(h),I.set(h,Date.now()),console.log(`SSE connection established for user ${c.id} (${h}). Total connections: ${d.size}`),a.res.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"});let f=()=>{I.set(h,Date.now())},y=setInterval(()=>{if(!a.res.writable){clearInterval(y);return}f(),a.res.write(`: ping | |
| `)},3e4);a.res.write(`: | |
| `),a.res.write(`data: ${JSON.stringify({type:"CONNECTED",payload:{message:"SSE connection established",timestamp:new Date().toISOString(),userId:c.id,connectionId:h}})} | |
| `),f();let b=s.subscribeToEvents(p=>{if(!a.res.writable){b();return}try{f(),a.res.write(`data: ${JSON.stringify(p)} | |
| `)}catch(w){console.error("Error sending SSE event:",w),S()}}),S=()=>{let p=B.get(c.id);p&&(p.delete(h),I.delete(h),console.log(`Connection ${h} for user ${c.id} cleaned up. Remaining: ${p.size}`),p.size===0&&B.delete(c.id)),b(),clearInterval(y),a.res.writable&&a.res.end()};return a.req.on("close",S),a.req.on("error",p=>{console.error(`SSE connection error for user ${c.id}:`,p),S()}),a.res.on("error",p=>{console.error(`SSE response error for user ${c.id}:`,p),S()}),{statusCode:200,_handled:!0,body:null}});async function l(a,c){let u={error:"Unauthorized",message:"Authentication required"},h=a.headers.authorization;if(!h||!h.startsWith("Bearer "))return a.status(401).json(u);let d=h.split(" ")[1];if(!d)return a.status(401).json(u);let f=r.verify(d),y=await s.getUserByEmail(f.email||f.sub);return a.state.user=y,c()}n.start()}function Fe(e){for(let t of e){let r=`${process.cwd()}/uploads`,s=ne(r,t);Nt(s)}}var G=class extends U{db;mikroDb;constructor(t){super(),this.mikroDb=t,this.db=new oe(t)}async start(){await this.mikroDb.start()}async close(){await this.mikroDb.close()}},oe=class{db;tableName;constructor(t,r="mikrochat_db"){this.db=t,this.tableName=r}async get(t){try{let r=await this.db.get({tableName:this.tableName,key:t});return r??null}catch(r){return console.error(`Error getting key ${t}:`,r),null}}async set(t,r,s){let i=s?Date.now()+s*1e3:0;await this.db.write({tableName:this.tableName,key:t,value:r,expiration:i})}async delete(t){await this.db.delete({tableName:this.tableName,key:t})}async list(t){try{return(await this.db.get({tableName:this.tableName})||[]).filter(i=>Array.isArray(i)&&typeof i[0]=="string"&&i[0].startsWith(t)).map(i=>i[1].value)}catch(r){return console.error(`Error listing with prefix ${t}:`,r),[]}}};var m=je(),Oe={configFilePath:"mikrochat.config.json",args:process.argv,options:[{flag:"--dev",path:"devMode",defaultValue:m.devMode,isFlag:!0},{flag:"--jwtSecret",path:"auth.jwtSecret",defaultValue:m.auth.jwtSecret},{flag:"--magicLinkExpirySeconds",path:"auth.magicLinkExpirySeconds",defaultValue:m.auth.magicLinkExpirySeconds},{flag:"--jwtExpirySeconds",path:"auth.jwtExpirySeconds",defaultValue:m.auth.jwtExpirySeconds},{flag:"--refreshTokenExpirySeconds",path:"auth.refreshTokenExpirySeconds",defaultValue:m.auth.refreshTokenExpirySeconds},{flag:"--maxActiveSessions",path:"auth.maxActiveSessions",defaultValue:m.auth.maxActiveSessions},{flag:"--appUrl",path:"auth.appUrl",defaultValue:m.auth.appUrl},{flag:"--isInviteRequired",path:"auth.isInviteRequired",isFlag:!0,defaultValue:m.auth.isInviteRequired},{flag:"--debug",path:"auth.debug",isFlag:!0,defaultValue:m.auth.debug},{flag:"--emailSubject",path:"email.emailSubject",defaultValue:"Your Secure Login Link"},{flag:"--emailHost",path:"email.host",defaultValue:m.email.host},{flag:"--emailUser",path:"email.user",defaultValue:m.email.user},{flag:"--emailPassword",path:"email.password",defaultValue:m.email.password},{flag:"--emailPort",path:"email.port",defaultValue:m.email.host},{flag:"--emailSecure",path:"email.secure",isFlag:!0,defaultValue:m.email.secure},{flag:"--emailMaxRetries",path:"email.maxRetries",defaultValue:m.email.maxRetries},{flag:"--debug",path:"email.debug",isFlag:!0,defaultValue:m.email.debug},{flag:"--db",path:"storage.databaseDirectory",defaultValue:m.storage.databaseDirectory},{flag:"--encryptionKey",path:"storage.encryptionKey",defaultValue:m.storage.encryptionKey},{flag:"--debug",path:"storage.debug",defaultValue:m.storage.debug},{flag:"--port",path:"server.port",defaultValue:m.server.port},{flag:"--host",path:"server.host",defaultValue:m.server.host},{flag:"--https",path:"server.useHttps",isFlag:!0,defaultValue:m.server.useHttps},{flag:"--http2",path:"server.useHttp2",isFlag:!0,defaultValue:m.server.useHttp2},{flag:"--cert",path:"server.sslCert",defaultValue:m.server.sslCert},{flag:"--key",path:"server.sslKey",defaultValue:m.server.sslKey},{flag:"--ca",path:"server.sslCa",defaultValue:m.server.sslCa},{flag:"--ratelimit",path:"server.rateLimit.enabled",defaultValue:m.server.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"server.rateLimit.requestsPerMinute",defaultValue:m.server.rateLimit.requestsPerMinute},{flag:"--allowed",path:"server.allowedDomains",defaultValue:m.server.allowedDomains,parser:T.array},{flag:"--debug",path:"server.debug",isFlag:!0,defaultValue:m.server.debug},{flag:"--initialUserId",path:"chat.initialUser.id",defaultValue:m.chat.initialUser.id},{flag:"--initialUserUsername",path:"chat.initialUser.userName",defaultValue:m.chat.initialUser.userName},{flag:"--initialUserEmail",path:"chat.initialUser.email",defaultValue:m.chat.initialUser.email},{flag:"--messageRetentionDays",path:"chat.messageRetentionDays",defaultValue:m.chat.messageRetentionDays},{flag:"--maxMessagesPerChannel",path:"chat.maxMessagesPerChannel",defaultValue:m.chat.maxMessagesPerChannel}]};async function Ft(){let e=Ht(),t=new j(e.storage);await t.start();let r=new ye(t),s=new G(t),i=new we(e.email),o=new pe(e.auth,i,r),n=new K(e.chat,s);await He({config:e.server,auth:o,chat:n,devMode:e.devMode,isInviteRequired:e.auth.isInviteRequired})}function Ht(){let e=new k(Oe).get();return e.auth.debug&&console.log("Using configuration:",e),e}Ft(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment