Skip to content

Instantly share code, notes, and snippets.

@mikaelvesavuori
Last active April 3, 2025 18:35
Show Gist options
  • Select an option

  • Save mikaelvesavuori/f838c02e4da491ce53368b52264330d2 to your computer and use it in GitHub Desktop.

Select an option

Save mikaelvesavuori/f838c02e4da491ce53368b52264330d2 to your computer and use it in GitHub Desktop.
MikroChat bundled
// 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.";if(await s.getUserByEmail(c)){let d=await r.createMagicLink({email:c,ip:a.req.socket.remoteAddress});if(!d)return a.json({error:"Failed to create magic link"},400);u=d.message}return 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