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.

Revisions

  1. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions mikrochat.bundled.mjs
    Original file line number Diff line number Diff line change
    @@ -1055,7 +1055,10 @@ var MikroMail = class {
    `Warning: No MX records found for recipient domain: ${recipient}`
    );
    }
    console.log('BEFORE SENDING')
    const result = await this.smtpClient.sendEmail(emailOptions);
    console.log('EMAIL RESULT', result)
    console.log('AFTER SENDING')
    if (result.success) console.log(`Message ID: ${result.messageId}`);
    else console.error(`Failed to send email: ${result.error}`);
    await this.smtpClient.close();
  2. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 1750 additions and 1044 deletions.
    2,794 changes: 1,750 additions & 1,044 deletions mikrochat.bundled.mjs
    1,750 additions, 1,044 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  3. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 69 additions and 68 deletions.
    137 changes: 69 additions & 68 deletions mikrochat.bundled.mjs
    Original file line number Diff line number Diff line change
    @@ -465,6 +465,7 @@ var SMTPClient = class {
    retryDelay: config.retryDelay ?? 1e3,
    skipAuthentication: config.skipAuthentication || false
    };
    console.log('SMTP CONFIG', this.config)
    this.socket = null;
    this.connected = false;
    this.lastCommand = "";
    @@ -1013,7 +1014,7 @@ var MikroMail = class {

    // node_modules/mikroauth/lib/index.mjs
    var g = () => {
    let o = T(process.env.DEBUG) || false;
    const o = T(process.env.DEBUG) || false;
    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: o }, email: { emailSubject: "Your Secure Login Link", user: process.env.EMAIL_USER || "", host: process.env.EMAIL_HOST || "", password: process.env.EMAIL_PASSWORD || "", port: 465, secure: true, maxRetries: 2, debug: o }, storage: { databaseDirectory: "mikroauth", encryptionKey: process.env.STORAGE_KEY || "", debug: o }, server: { port: Number(process.env.PORT) || 3e3, host: process.env.HOST || "0.0.0.0", useHttps: false, useHttp2: false, sslCert: "", sslKey: "", sslCa: "", rateLimit: { enabled: true, requestsPerMinute: 100 }, allowedDomains: ["*"], debug: o } };
    };
    function T(o) {
    @@ -1027,17 +1028,17 @@ var y = class {
    this.secret = e;
    }
    sign(e, t = {}) {
    let r = { alg: this.algorithm, typ: "JWT" }, i = Math.floor(Date.now() / 1e3), s = { ...e, iat: i };
    const r = { alg: this.algorithm, typ: "JWT" }, i = Math.floor(Date.now() / 1e3), s = { ...e, iat: i };
    t.exp !== void 0 && (s.exp = i + t.exp), t.notBefore !== void 0 && (s.nbf = i + t.notBefore), t.issuer && (s.iss = t.issuer), t.audience && (s.aud = t.audience), t.subject && (s.sub = t.subject), t.jwtid && (s.jti = t.jwtid);
    let a = this.base64UrlEncode(JSON.stringify(r)), d = this.base64UrlEncode(JSON.stringify(s)), l = `${a}.${d}`, c = this.createSignature(l);
    const a = this.base64UrlEncode(JSON.stringify(r)), d = this.base64UrlEncode(JSON.stringify(s)), l = `${a}.${d}`, c = this.createSignature(l);
    return `${l}.${c}`;
    }
    verify(e, t = {}) {
    let r = this.decode(e);
    const r = this.decode(e);
    if (r.header.alg !== this.algorithm) throw new Error(`Invalid algorithm. Expected ${this.algorithm}, got ${r.header.alg}`);
    let [i, s] = e.split("."), a = `${i}.${s}`;
    const [i, s] = e.split("."), a = `${i}.${s}`;
    if (this.createSignature(a) !== r.signature) throw new Error("Invalid signature");
    let l = r.payload, c = Math.floor(Date.now() / 1e3), u = t.clockTolerance || 0;
    const l = r.payload, c = Math.floor(Date.now() / 1e3), u = t.clockTolerance || 0;
    if (l.exp !== void 0 && l.exp + u < c) throw new Error("Token expired");
    if (l.nbf !== void 0 && l.nbf - u > c) throw new Error("Token not yet valid");
    if (t.issuer && l.iss !== t.issuer) throw new Error("Invalid issuer");
    @@ -1046,17 +1047,17 @@ var y = class {
    return l;
    }
    decode(e) {
    let t = e.split(".");
    const t = e.split(".");
    if (t.length !== 3) throw new Error("Invalid token format");
    try {
    let [r, i, s] = t, a = JSON.parse(this.base64UrlDecode(r)), d = JSON.parse(this.base64UrlDecode(i));
    const [r, i, s] = t, a = JSON.parse(this.base64UrlDecode(r)), d = JSON.parse(this.base64UrlDecode(i));
    return { header: a, payload: d, signature: s };
    } catch {
    throw new Error("Failed to decode token");
    }
    }
    createSignature(e) {
    let t = f.createHmac("sha256", this.secret).update(e).digest();
    const t = f.createHmac("sha256", this.secret).update(e).digest();
    return this.base64UrlEncode(t);
    }
    base64UrlEncode(e) {
    @@ -1199,65 +1200,65 @@ var m = class {
    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));
    const e = Date.now();
    for (const [t, r] of this.data.entries()) r.expiry && r.expiry < e && (this.data.delete(t), this.expiryEmitter.emit("expired", t));
    for (const [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 i = r ? Date.now() + r * 1e3 : null;
    const i = r ? Date.now() + r * 1e3 : null;
    this.data.set(e, { value: t, expiry: i });
    }
    async get(e) {
    let t = this.data.get(e);
    const 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 i = this.collections.get(e);
    const i = this.collections.get(e);
    r && (i.expiry = Date.now() + r * 1e3), i.items.push(t);
    }
    async removeFromCollection(e, t) {
    let r = this.collections.get(e);
    const r = this.collections.get(e);
    r && (r.items = r.items.filter((i) => i !== t));
    }
    async getCollection(e) {
    let t = this.collections.get(e);
    const t = this.collections.get(e);
    return t ? [...t.items] : [];
    }
    async getCollectionSize(e) {
    let t = this.collections.get(e);
    const t = this.collections.get(e);
    return t ? t.items.length : 0;
    }
    async removeOldestFromCollection(e) {
    let t = this.collections.get(e);
    const 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}$`), i = Array.from(this.data.keys()).filter((a) => r.test(a)), s = Array.from(this.collections.keys()).filter((a) => r.test(a));
    const t = e.replace(/\*/g, ".*").replace(/\?/g, "."), r = new RegExp(`^${t}$`), i = Array.from(this.data.keys()).filter((a) => r.test(a)), s = Array.from(this.collections.keys()).filter((a) => r.test(a));
    return [.../* @__PURE__ */ new Set([...i, ...s])];
    }
    };
    function S(o) {
    if (!o || o.trim() === "" || (o.match(/@/g) || []).length !== 1) return false;
    let [t, r] = o.split("@");
    const [t, r] = o.split("@");
    return !(!t || !r || o.includes("..") || !I(t) || !$(r));
    }
    function I(o) {
    return o.startsWith('"') && o.endsWith('"') ? !o.slice(1, -1).includes('"') : o.length > 64 || o.startsWith(".") || o.endsWith(".") ? false : /^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$/.test(o);
    }
    function $(o) {
    if (o.startsWith("[") && o.endsWith("]")) {
    let t = o.slice(1, -1);
    const t = o.slice(1, -1);
    return t.startsWith("IPv6:") ? R(t.slice(5)) : M(t);
    }
    let e = o.split(".");
    const e = o.split(".");
    if (e.length === 0) return false;
    for (let t of e) if (!t || t.length > 63 || !/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(t)) return false;
    for (const t of e) if (!t || t.length > 63 || !/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(t)) return false;
    if (e.length > 1) {
    let t = e[e.length - 1];
    const t = e[e.length - 1];
    if (!/^[a-zA-Z]{2,}$/.test(t)) return false;
    }
    return true;
    @@ -1267,12 +1268,12 @@ function M(o) {
    }
    function R(o) {
    if (!/^[a-fA-F0-9:]+$/.test(o)) return false;
    let t = o.split(":");
    const t = o.split(":");
    return !(t.length < 2 || t.length > 8);
    }
    var n = g();
    var P = (o) => {
    let e = { configFilePath: "mikroauth.config.json", args: process.argv, options: [{ flag: "--jwtSecret", path: "auth.jwtSecret", defaultValue: n.auth.jwtSecret }, { flag: "--magicLinkExpirySeconds", path: "auth.magicLinkExpirySeconds", defaultValue: n.auth.magicLinkExpirySeconds }, { flag: "--jwtExpirySeconds", path: "auth.jwtExpirySeconds", defaultValue: n.auth.jwtExpirySeconds }, { flag: "--refreshTokenExpirySeconds", path: "auth.refreshTokenExpirySeconds", defaultValue: n.auth.refreshTokenExpirySeconds }, { flag: "--maxActiveSessions", path: "auth.maxActiveSessions", defaultValue: n.auth.maxActiveSessions }, { flag: "--appUrl", path: "auth.appUrl", defaultValue: n.auth.appUrl }, { flag: "--debug", path: "auth.debug", isFlag: true, defaultValue: n.auth.debug }, { flag: "--emailSubject", path: "email.emailSubject", defaultValue: "Your Secure Login Link" }, { flag: "--emailHost", path: "email.host", defaultValue: n.email.host }, { flag: "--emailUser", path: "email.user", defaultValue: n.email.user }, { flag: "--emailPassword", path: "email.password", defaultValue: n.email.password }, { flag: "--emailPort", path: "email.port", defaultValue: n.email.port }, { flag: "--emailSecure", path: "email.secure", isFlag: true, defaultValue: n.email.secure }, { flag: "--emailMaxRetries", path: "email.maxRetries", defaultValue: n.email.maxRetries }, { flag: "--debug", path: "email.debug", isFlag: true, defaultValue: n.email.debug }, { flag: "--dir", path: "storage.databaseDirectory", defaultValue: n.storage.databaseDirectory }, { flag: "--encryptionKey", path: "storage.encryptionKey", defaultValue: n.storage.encryptionKey }, { flag: "--debug", path: "storage.debug", defaultValue: n.storage.debug }, { flag: "--port", path: "server.port", defaultValue: n.server.port }, { flag: "--host", path: "server.host", defaultValue: n.server.host }, { flag: "--https", path: "server.useHttps", isFlag: true, defaultValue: n.server.useHttps }, { flag: "--https", path: "server.useHttp2", isFlag: true, defaultValue: n.server.useHttp2 }, { flag: "--cert", path: "server.sslCert", defaultValue: n.server.sslCert }, { flag: "--key", path: "server.sslKey", defaultValue: n.server.sslKey }, { flag: "--ca", path: "server.sslCa", defaultValue: n.server.sslCa }, { flag: "--ratelimit", path: "server.rateLimit.enabled", defaultValue: n.server.rateLimit.enabled, isFlag: true }, { flag: "--rps", path: "server.rateLimit.requestsPerMinute", defaultValue: n.server.rateLimit.requestsPerMinute }, { flag: "--allowed", path: "server.allowedDomains", defaultValue: n.server.allowedDomains, parser: parsers.array }, { flag: "--debug", path: "server.debug", isFlag: true, defaultValue: n.server.debug }] };
    const e = { configFilePath: "mikroauth.config.json", args: process.argv, options: [{ flag: "--jwtSecret", path: "auth.jwtSecret", defaultValue: n.auth.jwtSecret }, { flag: "--magicLinkExpirySeconds", path: "auth.magicLinkExpirySeconds", defaultValue: n.auth.magicLinkExpirySeconds }, { flag: "--jwtExpirySeconds", path: "auth.jwtExpirySeconds", defaultValue: n.auth.jwtExpirySeconds }, { flag: "--refreshTokenExpirySeconds", path: "auth.refreshTokenExpirySeconds", defaultValue: n.auth.refreshTokenExpirySeconds }, { flag: "--maxActiveSessions", path: "auth.maxActiveSessions", defaultValue: n.auth.maxActiveSessions }, { flag: "--appUrl", path: "auth.appUrl", defaultValue: n.auth.appUrl }, { flag: "--debug", path: "auth.debug", isFlag: true, defaultValue: n.auth.debug }, { flag: "--emailSubject", path: "email.emailSubject", defaultValue: "Your Secure Login Link" }, { flag: "--emailHost", path: "email.host", defaultValue: n.email.host }, { flag: "--emailUser", path: "email.user", defaultValue: n.email.user }, { flag: "--emailPassword", path: "email.password", defaultValue: n.email.password }, { flag: "--emailPort", path: "email.port", defaultValue: n.email.port }, { flag: "--emailSecure", path: "email.secure", isFlag: true, defaultValue: n.email.secure }, { flag: "--emailMaxRetries", path: "email.maxRetries", defaultValue: n.email.maxRetries }, { flag: "--debug", path: "email.debug", isFlag: true, defaultValue: n.email.debug }, { flag: "--dir", path: "storage.databaseDirectory", defaultValue: n.storage.databaseDirectory }, { flag: "--encryptionKey", path: "storage.encryptionKey", defaultValue: n.storage.encryptionKey }, { flag: "--debug", path: "storage.debug", defaultValue: n.storage.debug }, { flag: "--port", path: "server.port", defaultValue: n.server.port }, { flag: "--host", path: "server.host", defaultValue: n.server.host }, { flag: "--https", path: "server.useHttps", isFlag: true, defaultValue: n.server.useHttps }, { flag: "--https", path: "server.useHttp2", isFlag: true, defaultValue: n.server.useHttp2 }, { flag: "--cert", path: "server.sslCert", defaultValue: n.server.sslCert }, { flag: "--key", path: "server.sslKey", defaultValue: n.server.sslKey }, { flag: "--ca", path: "server.sslCa", defaultValue: n.server.sslCa }, { flag: "--ratelimit", path: "server.rateLimit.enabled", defaultValue: n.server.rateLimit.enabled, isFlag: true }, { flag: "--rps", path: "server.rateLimit.requestsPerMinute", defaultValue: n.server.rateLimit.requestsPerMinute }, { flag: "--allowed", path: "server.allowedDomains", defaultValue: n.server.allowedDomains, parser: parsers.array }, { flag: "--debug", path: "server.debug", isFlag: true, defaultValue: n.server.debug }] };
    return o && (e.config = o), e;
    };
    var v = { linkSent: "If a matching account was found, a magic link has been sent.", revokedSuccess: "All other sessions revoked successfully.", logoutSuccess: "Logged out successfully." };
    @@ -1283,14 +1284,14 @@ var E = class {
    jwtService;
    templates;
    constructor(e, t, r) {
    let i = new MikroConf(P({ auth: e.auth, email: e.email })).get();
    const i = new MikroConf(P({ auth: e.auth, email: e.email })).get();
    i.auth.debug && console.log("Using configuration:", i), this.config = i, this.email = t || new h(), this.storage = r || new m(), this.jwtService = new y(i.auth.jwtSecret), this.templates = new w(i?.auth.templates), this.checkIfUsingDefaultCredentialsInProduction();
    }
    checkIfUsingDefaultCredentialsInProduction() {
    process.env.NODE_ENV === "production" && this.config.auth.jwtSecret === g().auth.jwtSecret && (console.error("WARNING: Using default secrets in production environment!"), process.exit(1));
    }
    generateToken(e) {
    let t = Date.now().toString(), r = f.randomBytes(32).toString("hex");
    const t = Date.now().toString(), r = f.randomBytes(32).toString("hex");
    return f.createHash("sha256").update(`${e}:${t}:${r}`).digest("hex");
    }
    generateJsonWebToken(e) {
    @@ -1300,65 +1301,65 @@ var E = class {
    return f.randomBytes(40).toString("hex");
    }
    async trackSession(e, t, r) {
    let i = `sessions:${e}`;
    const i = `sessions:${e}`;
    if (await this.storage.getCollectionSize(i) >= this.config.auth.maxActiveSessions) {
    let a = await this.storage.removeOldestFromCollection(i);
    const a = await this.storage.removeOldestFromCollection(i);
    a && await this.storage.delete(`refresh:${a}`);
    }
    await this.storage.addToCollection(i, t, this.config.auth.refreshTokenExpirySeconds), await this.storage.set(`refresh:${t}`, JSON.stringify(r), this.config.auth.refreshTokenExpirySeconds);
    }
    generateMagicLinkUrl(e) {
    let { token: t, email: r } = e;
    const { token: t, email: r } = e;
    try {
    return new URL(this.config.auth.appUrl), `${this.config.auth.appUrl}?token=${encodeURIComponent(t)}&email=${encodeURIComponent(r)}`;
    } catch {
    throw new Error("Invalid base URL configuration");
    }
    }
    async createMagicLink(e) {
    let { email: t, ip: r } = e;
    const { email: t, ip: r } = e;
    if (!S(t)) throw new Error("Valid email required");
    try {
    let i = this.generateToken(t), s = `magic_link:${i}`, a = { email: t, ipAddress: r || "unknown", createdAt: Date.now() };
    const i = this.generateToken(t), s = `magic_link:${i}`, a = { email: t, ipAddress: r || "unknown", createdAt: Date.now() };
    await this.storage.set(s, JSON.stringify(a), this.config.auth.magicLinkExpirySeconds);
    let d = await this.storage.findKeys("magic_link:*");
    for (let u of d) {
    const d = await this.storage.findKeys("magic_link:*");
    for (const u of d) {
    if (u === s) continue;
    let p = await this.storage.get(u);
    const p = await this.storage.get(u);
    if (p) try {
    JSON.parse(p).email === t && await this.storage.delete(u);
    } catch {
    }
    }
    console.log('this.config.email',this.config.email)
    let l = this.generateMagicLinkUrl({ token: i, email: t }), c = Math.ceil(this.config.auth.magicLinkExpirySeconds / 60);
    const l = this.generateMagicLinkUrl({ token: i, email: t }), c = Math.ceil(this.config.auth.magicLinkExpirySeconds / 60);
    return await this.email.sendMail({ from: this.config.email.user, to: t, subject: this.config.email.emailSubject, text: this.templates.getText(l, c), html: this.templates.getHtml(l, c) }), { message: v.linkSent };
    } catch (i) {
    throw console.error(`Failed to process magic link request: ${i}`), new Error("Failed to process magic link request");
    }
    }
    async verifyToken(e) {
    let { token: t, email: r } = e;
    const { token: t, email: r } = e;
    try {
    let i = `magic_link:${t}`, s = await this.storage.get(i);
    const i = `magic_link:${t}`, s = await this.storage.get(i);
    if (!s) throw new Error("Invalid or expired token");
    let a = JSON.parse(s);
    const a = JSON.parse(s);
    if (a.email !== r) throw new Error("Email mismatch");
    let d = a.username, l = a.role;
    const d = a.username, l = a.role;
    await this.storage.delete(i);
    let c = f.randomBytes(16).toString("hex"), u = this.generateRefreshToken(), p = { sub: r, username: d, role: l, jti: c, lastLogin: a.createdAt, metadata: { ip: a.ipAddress }, exp: Math.floor(Date.now() / 1e3) + 60 * 60 * 24 }, b = this.jwtService.sign(p, { exp: this.config.auth.jwtExpirySeconds });
    const c = f.randomBytes(16).toString("hex"), u = this.generateRefreshToken(), p = { sub: r, username: d, role: l, jti: c, lastLogin: a.createdAt, metadata: { ip: a.ipAddress }, exp: Math.floor(Date.now() / 1e3) + 60 * 60 * 24 }, b = this.jwtService.sign(p, { exp: this.config.auth.jwtExpirySeconds });
    return await this.trackSession(r, u, { ...a, tokenId: c, createdAt: Date.now() }), { accessToken: b, refreshToken: u, exp: this.config.auth.jwtExpirySeconds, tokenType: "Bearer" };
    } catch (i) {
    throw console.error("Token verification error:", i), new Error("Verification failed");
    }
    }
    async refreshAccessToken(e) {
    try {
    let t = await this.storage.get(`refresh:${e}`);
    const t = await this.storage.get(`refresh:${e}`);
    if (!t) throw new Error("Invalid or expired refresh token");
    let r = JSON.parse(t), i = r.email;
    const r = JSON.parse(t), i = r.email;
    if (!i) throw new Error("Invalid refresh token data");
    let s = r.username, a = r.role, d = f.randomBytes(16).toString("hex"), l = { sub: i, username: s, role: a, jti: d, lastLogin: r.lastLogin || r.createdAt, metadata: { ip: r.ipAddress } }, c = this.jwtService.sign(l, { exp: this.config.auth.jwtExpirySeconds });
    const s = r.username, a = r.role, d = f.randomBytes(16).toString("hex"), l = { sub: i, username: s, role: a, jti: d, lastLogin: r.lastLogin || r.createdAt, metadata: { ip: r.ipAddress } }, c = this.jwtService.sign(l, { exp: this.config.auth.jwtExpirySeconds });
    return r.lastUsed = Date.now(), await this.storage.set(`refresh:${e}`, JSON.stringify(r), this.config.auth.refreshTokenExpirySeconds), { accessToken: c, refreshToken: e, exp: this.config.auth.jwtExpirySeconds, tokenType: "Bearer" };
    } catch (t) {
    throw console.error("Token refresh error:", t), new Error("Token refresh failed");
    @@ -1374,12 +1375,12 @@ var E = class {
    async logout(e) {
    try {
    if (!e || typeof e != "string") throw new Error("Refresh token is required");
    let t = await this.storage.get(`refresh:${e}`);
    const t = await this.storage.get(`refresh:${e}`);
    if (!t) return { message: v.logoutSuccess };
    let i = JSON.parse(t).email;
    const i = JSON.parse(t).email;
    if (!i) throw new Error("Invalid refresh token data");
    await this.storage.delete(`refresh:${e}`);
    let s = `sessions:${i}`;
    const s = `sessions:${i}`;
    return await this.storage.removeFromCollection(s, e), { message: v.logoutSuccess };
    } catch (t) {
    throw console.error("Logout error:", t), new Error("Logout failed");
    @@ -1388,11 +1389,11 @@ var E = class {
    async getSessions(e) {
    try {
    if (!e.user?.email) throw new Error("User not authenticated");
    let t = e.user.email, r = e.body?.refreshToken, i = `sessions:${t}`, a = (await this.storage.getCollection(i)).map(async (l) => {
    const t = e.user.email, r = e.body?.refreshToken, i = `sessions:${t}`, a = (await this.storage.getCollection(i)).map(async (l) => {
    try {
    let c = await this.storage.get(`refresh:${l}`);
    const c = await this.storage.get(`refresh:${l}`);
    if (!c) return await this.storage.removeFromCollection(i, l), null;
    let u = JSON.parse(c);
    const u = JSON.parse(c);
    return { id: `${l.substring(0, 8)}...`, createdAt: u.createdAt || 0, lastLogin: u.lastLogin || u.createdAt || 0, lastUsed: u.lastUsed || u.createdAt || 0, metadata: { ip: u.ipAddress }, isCurrentSession: l === r };
    } catch {
    return await this.storage.removeFromCollection(i, l), null;
    @@ -1406,20 +1407,20 @@ var E = class {
    async revokeSessions(e) {
    try {
    if (!e.user?.email) throw new Error("User not authenticated");
    let t = e.user.email, r = e.body?.refreshToken, i = `sessions:${t}`, s = await this.storage.getCollection(i);
    for (let a of s) r && a === r || await this.storage.delete(`refresh:${a}`);
    const t = e.user.email, r = e.body?.refreshToken, i = `sessions:${t}`, s = await this.storage.getCollection(i);
    for (const a of s) r && a === r || await this.storage.delete(`refresh:${a}`);
    return await this.storage.delete(i), r && await this.storage.get(`refresh:${r}`) && await this.storage.addToCollection(i, r, this.config.auth.refreshTokenExpirySeconds), { message: v.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;
    const r = e.headers?.authorization;
    if (!r || !r.startsWith("Bearer ")) throw new Error("Authentication required");
    let i = r.split(" ")[1];
    const i = r.split(" ")[1];
    try {
    let s = this.verify(i);
    const s = this.verify(i);
    e.user = { email: s.sub }, t();
    } catch {
    throw new Error("Invalid or expired token");
    @@ -1444,48 +1445,48 @@ var x = class {
    await this.db.close();
    }
    async set(e, t, r) {
    let i = `${this.PREFIX_KV}${e}`, s = r ? Date.now() + r * 1e3 : void 0;
    const i = `${this.PREFIX_KV}${e}`, s = r ? Date.now() + r * 1e3 : void 0;
    await this.db.write({ tableName: this.TABLE_NAME, key: i, value: t, expiration: s });
    }
    async get(e) {
    let t = `${this.PREFIX_KV}${e}`, r = await this.db.get({ tableName: this.TABLE_NAME, key: t });
    const t = `${this.PREFIX_KV}${e}`, r = await this.db.get({ tableName: this.TABLE_NAME, key: t });
    return r || null;
    }
    async delete(e) {
    let t = `${this.PREFIX_KV}${e}`;
    const t = `${this.PREFIX_KV}${e}`;
    await this.db.delete({ tableName: this.TABLE_NAME, key: t });
    }
    async addToCollection(e, t, r) {
    let i = `${this.PREFIX_COLLECTION}${e}`, s = await this.db.get({ tableName: this.TABLE_NAME, key: i }), a = [];
    s && (a = JSON.parse(s)), a.includes(t) || a.push(t);
    let d = r ? Date.now() + r * 1e3 : void 0;
    const d = r ? Date.now() + r * 1e3 : void 0;
    await this.db.write({ tableName: this.TABLE_NAME, key: i, value: JSON.stringify(a), expiration: d });
    }
    async removeFromCollection(e, t) {
    let r = `${this.PREFIX_COLLECTION}${e}`, i = await this.db.get({ tableName: this.TABLE_NAME, key: r });
    const r = `${this.PREFIX_COLLECTION}${e}`, i = await this.db.get({ tableName: this.TABLE_NAME, key: r });
    if (!i) return;
    let s = JSON.parse(i);
    s = s.filter((a) => a !== t), await this.db.write({ tableName: this.TABLE_NAME, key: r, value: JSON.stringify(s) });
    }
    async getCollection(e) {
    let t = `${this.PREFIX_COLLECTION}${e}`, r = await this.db.get({ tableName: this.TABLE_NAME, key: t });
    const 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 });
    const t = `${this.PREFIX_COLLECTION}${e}`, r = await this.db.get({ tableName: this.TABLE_NAME, key: t });
    if (!r) return null;
    let i = JSON.parse(r);
    const i = JSON.parse(r);
    if (i.length === 0) return null;
    let s = i.shift();
    const s = i.shift();
    return await this.db.write({ tableName: this.TABLE_NAME, key: t, value: JSON.stringify(i) }), s;
    }
    async findKeys(e) {
    let t = e.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, "."), r = new RegExp(`^${t}$`);
    const t = e.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, "."), r = new RegExp(`^${t}$`);
    return (await this.db.get({ tableName: this.TABLE_NAME })).filter((s) => {
    let a = s[0];
    const a = s[0];
    return typeof a == "string" && a.startsWith(this.PREFIX_KV);
    }).map((s) => s[0].substring(this.PREFIX_KV.length)).filter((s) => r.test(s));
    }
  4. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 5770 additions and 36 deletions.
    5,806 changes: 5,770 additions & 36 deletions mikrochat.bundled.mjs
    5,770 additions, 36 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  5. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 36 additions and 6492 deletions.
    6,528 changes: 36 additions & 6,492 deletions mikrochat.bundled.mjs
    36 additions, 6,492 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  6. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions mikrochat.bundled.mjs
    Original file line number Diff line number Diff line change
    @@ -1676,6 +1676,10 @@ var E = class {
    console.log('result', l);
    console.log(666);

    console.log('THIS CONFIG', this.config);

    console.log('DEFAULTS', g());

    return (
    await this.email.sendMail({
    from: process.env.EMAIL_FROM || g().email.user,
  7. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions mikrochat.bundled.mjs
    Original file line number Diff line number Diff line change
    @@ -1642,8 +1642,8 @@ var E = class {
    async createMagicLink(e) {
    console.log('createMagicLink');
    const { email: t, ip: r } = e;
    console.log('email', email);
    console.log('ip', ip);
    console.log('email', t);
    console.log('ip', r);
    if (!S(t)) throw new Error('Valid email required');
    try {
    console.log(111);
  8. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 6488 additions and 36 deletions.
    6,524 changes: 6,488 additions & 36 deletions mikrochat.bundled.mjs
    6,488 additions, 36 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  9. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion mikrochat.bundled.mjs
    1 addition, 1 deletion not shown because the diff is too large. Please use a local Git client to view these changes.
  10. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions mikrochat.bundled.mjs
    2 additions, 2 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  11. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions mikrochat.bundled.mjs
    2 additions, 2 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  12. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion mikrochat.bundled.mjs
    1 addition, 1 deletion not shown because the diff is too large. Please use a local Git client to view these changes.
  13. mikaelvesavuori revised this gist Apr 3, 2025. 1 changed file with 24 additions and 24 deletions.
    48 changes: 24 additions & 24 deletions mikrochat.bundled.mjs
    24 additions, 24 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  14. mikaelvesavuori revised this gist Apr 3, 2025. No changes.
  15. mikaelvesavuori created this gist Mar 29, 2025.
    123 changes: 123 additions & 0 deletions mikrochat.bundled.mjs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,123 @@
    // MikroChat - See LICENSE file for copyright and license details.
    var V=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}`,n=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:t,expiration:n})}async get(e){let t=`${this.PREFIX_KV}${e}`,r=await this.db.get({tableName:this.TABLE_NAME,key:t});return r||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}`;console.log(`Adding to collection | Key: "${s}" | Item: "${t}" |`);let n=await this.db.get({tableName:this.TABLE_NAME,key:s}),i=[];n&&(i=JSON.parse(n)),i.includes(t)||i.push(t);let o=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:JSON.stringify(i),expiration:o})}async removeFromCollection(e,t){let r=`${this.PREFIX_COLLECTION}${e}`;console.log("Removing from collection",r);let s=await this.db.get({tableName:this.TABLE_NAME,key:r});if(!s)return;let n=JSON.parse(s);n=n.filter(i=>i!==t),await this.db.write({tableName:this.TABLE_NAME,key:r,value:JSON.stringify(n)})}async getCollection(e){let t=`${this.PREFIX_COLLECTION}${e}`;console.log("Getting collection",t);let r=await this.db.get({tableName:this.TABLE_NAME,key:t});return r?JSON.parse(r):[]}async getCollectionSize(e){let t=await this.getCollection(e);return console.log(`The collection size of "${e}" is ${t.length}`),t.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 n=s.shift();return await this.db.write({tableName:this.TABLE_NAME,key:t,value:JSON.stringify(s)}),n}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(n=>{let i=n[0];return typeof i=="string"&&i.startsWith(this.PREFIX_KV)}).map(n=>n[0].substring(this.PREFIX_KV.length)).filter(n=>r.test(n))}};var ee=class extends Error{constructor(e){super(),this.name="ValidationError",this.message=e,this.cause={statusCode:400}}};import{existsSync as Ne,readFileSync as je}from"node:fs";var te=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},n={};if(Ne(e))try{let o=je(e,"utf8");n=JSON.parse(o),console.log(`Loaded configuration from ${e}`)}catch(o){console.error(`Error reading config file: ${o instanceof Error?o.message:String(o)}`)}let i=this.parseCliArgs(t);return{...s,...r,...n,...i}}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 n=Number.parseInt(e[++r],10);Number.isNaN(n)||(t.port=n)}break;case"--secure":t.secure=!0;break;case"--debug":t.debug=!0;break;case"--retries":if(r+1<e.length){let n=Number.parseInt(e[++r],10);Number.isNaN(n)||(t.maxRetries=n)}break}return t}validate(){if(!this.config.host)throw new ee("Host value not found")}get(){return this.validate(),this.config}};import{promises as Re}from"node:dns";function C(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 n=r.slice(1,-1);return n.startsWith("IPv6:")?!0:/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/.test(n)}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 n of s)if(!n||n.length>63||!/^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?$/.test(n))return!1;return!0}catch{return!1}}async function Ve(e){try{let t=await Re.resolveMx(e);return!!t&&t.length>0}catch{return!1}}async function re(e){try{let t=e.split("@")[1];return t?await Ve(t):!1}catch{return!1}}import{Buffer as k}from"node:buffer";import F from"node:crypto";import Fe from"node:net";import He from"node:os";import se from"node:tls";var ie=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??He.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=se.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=Fe.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 n=s.toString().trim();this.log(`Server greeting: ${n}`),n.startsWith("220")?(this.connected=!0,this.secureMode=this.config.secure,t()):(r(new Error(`Unexpected server greeting: ${n}`)),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"},n=se.connect(s);n.once("error",i=>{this.log(`TLS upgrade error: ${i.message}`,!0),t(new Error(`STARTTLS error: ${i.message}`))}),n.once("secureConnect",()=>{this.log("Connection upgraded to TLS"),n.authorized?(this.socket=n,this.secureMode=!0,e()):t(new Error(`TLS certificate verification failed: ${n.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,n)=>{let i=setTimeout(()=>{this.socket?.removeListener("data",a),n(new Error(`Command timeout after ${r}ms: ${e}`))},r),o="",a=c=>{o+=c.toString();let l=o.split(`\r
    `);if(l.length>0&&l[l.length-1]===""){let u=l[l.length-2]||"",h=/^(\d{3})(.?)/.exec(u);h?.[1]&&h[2]!=="-"&&(this.socket?.removeListener("data",a),clearTimeout(i),this.log(`SMTP Response: ${o.trim()}`),h[1]===t.toString()?s(o.trim()):n(new Error(`SMTP Error: ${o.trim()}`)))}};this.socket.on("data",a),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 n=s.substr(4).toUpperCase();this.serverCapabilities.push(n)}}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=k.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(k.from(this.config.user).toString("base64"),334),await this.sendCommand(k.from(this.config.password).toString("base64"),235)}async authenticateCramMD5(){let e=await this.sendCommand("AUTH CRAM-MD5",334),t=k.from(e.substr(4),"base64").toString("utf8"),r=F.createHmac("md5",this.config.password);r.update(t);let s=r.digest("hex"),n=`${this.config.user} ${s}`,i=k.from(n).toString("base64");await this.sendCommand(i,235)}generateMessageId(){let e=F.randomBytes(16).toString("hex"),t=this.config.user.split("@")[1]||"localhost";return`<${e}@${t}>`}generateBoundary(){return`----=_NextPart_${F.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,n=Array.isArray(e.to)?e.to.join(", "):e.to,i=[`From: ${this.sanitizeHeader(s)}`,`To: ${this.sanitizeHeader(n)}`,`Subject: ${this.sanitizeHeader(e.subject)}`,`Message-ID: ${t}`,`Date: ${r}`,"MIME-Version: 1.0"];if(e.cc){let o=Array.isArray(e.cc)?e.cc.join(", "):e.cc;i.push(`Cc: ${this.sanitizeHeader(o)}`)}if(e.replyTo&&i.push(`Reply-To: ${this.sanitizeHeader(e.replyTo)}`),e.headers)for(let[o,a]of Object.entries(e.headers))/^[a-zA-Z0-9-]+$/.test(o)&&(/^(from|to|cc|bcc|subject|date|message-id)$/i.test(o)||i.push(`${o}: ${this.sanitizeHeader(a)}`));return i}createMultipartEmail(e){let{text:t,html:r}=e,s=this.createEmailHeaders(e),n=this.generateBoundary();return r&&t?(s.push(`Content-Type: multipart/alternative; boundary="${n}"`),`${s.join(`\r
    `)}\r
    \r
    --${n}\r
    Content-Type: text/plain; charset=utf-8\r
    \r
    ${t||""}\r
    \r
    --${n}\r
    Content-Type: text/html; charset=utf-8\r
    \r
    ${r||""}\r
    \r
    --${n}--\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,n=e.text||"",i=e.html||"";if(!t||!r||!s||!n&&!i)return{success:!1,error:"Missing required email parameters (from, to, subject, and either text or html)"};if(!C(t))return{success:!1,error:"Invalid email address format"};let o=Array.isArray(e.to)?e.to:[e.to];for(let a of o)if(!C(a))return{success:!1,error:`Invalid recipient email address format: ${a}`};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 o)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)C(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)C(h)&&await this.sendCommand(`RCPT TO:<${h}>`,250)}await this.sendCommand("DATA",354);let a=this.createMultipartEmail(e);if(a.length>this.maxEmailSize)return{success:!1,error:"Email size exceeds maximum allowed"};await this.sendCommand(`${a}\r
    .`,250);let c=/Message-ID: (.*)/i.exec(a);return{success:!0,messageId:c?c[1].trim():void 0,message:"Email sent successfully"}}catch(a){let c=a.message;if(this.log(`Error sending email: ${c}`,!0),c.includes("5.")||c.includes("Authentication failed")||c.includes("certificate")||this.retryCount>=this.config.maxRetries)return{success:!1,error:c};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 H=class{smtpClient;constructor(e){let t=new te(e).get(),r=new ie(t);this.smtpClient=r}async send(e){try{let t=Array.isArray(e.to)?e.to:[e.to];for(let s of t)await re(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 O=class{email;sender;constructor(e){this.sender=e.user,this.email=new H({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})}};function ne(e){return/^(?!.*\.\.)(([a-z0-9!#$%&'*+/=?^_`{|}~-]{1,64}(\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*)|("[^"]*"))@((\[(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}\])|(\[IPv6:[a-f0-9:]+\])|([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\.([a-z]{2,})))$/i.test(e)}var oe=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=[]}};import{EventEmitter as Oe}from"node:events";var ae=class{data=new Map;collections=new Map;expiryEmitter=new Oe;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(i=>r.test(i)),n=Array.from(this.collections.keys()).filter(i=>r.test(i));return[...new Set([...s,...n])]}};var w=()=>{let e=_e(process.env.DEBUG)||!1;return{auth:{jwtSecret:process.env.AUTH_JWT_SECRET||"your-jwt-secret",magicLinkExpirySeconds:900,jwtExpirySeconds:900,refreshTokenExpirySeconds:604800,maxActiveSessions:3,appUrl:process.env.APP_URL||"http://localhost:3000",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,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"],debug:e}}};function _e(e){return e==="true"||e===!0}import We from"node:crypto";var ce=class{algorithm="HS256";secret="HS256";constructor(e){if(process.env.NODE_ENV==="production"&&(!e||e.length<32||e===w().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),n={...e,iat:s};t.exp!==void 0&&(n.exp=s+t.exp),t.notBefore!==void 0&&(n.nbf=s+t.notBefore),t.issuer&&(n.iss=t.issuer),t.audience&&(n.aud=t.audience),t.subject&&(n.sub=t.subject),t.jwtid&&(n.jti=t.jwtid);let i=this.base64UrlEncode(JSON.stringify(r)),o=this.base64UrlEncode(JSON.stringify(n)),a=`${i}.${o}`,c=this.createSignature(a);return`${a}.${c}`}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,n]=e.split("."),i=`${s}.${n}`;if(this.createSignature(i)!==r.signature)throw new Error("Invalid signature");let a=r.payload,c=Math.floor(Date.now()/1e3),l=t.clockTolerance||0;if(a.exp!==void 0&&a.exp+l<c)throw new Error("Token expired");if(a.nbf!==void 0&&a.nbf-l>c)throw new Error("Token not yet valid");if(t.issuer&&a.iss!==t.issuer)throw new Error("Invalid issuer");if(t.audience&&a.aud!==t.audience)throw new Error("Invalid audience");if(t.subject&&a.sub!==t.subject)throw new Error("Invalid subject");return a}decode(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid token format");try{let[r,s,n]=t,i=JSON.parse(this.base64UrlDecode(r)),o=JSON.parse(this.base64UrlDecode(s));return{header:i,payload:o,signature:n}}catch{throw new Error("Failed to decode token")}}createSignature(e){let t=We.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()}};var _=class{static getText(e,t){return`
    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.
    `.trim()}static getHtml(e,t){return`
    <!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>
    `.trim()}};import S from"node:crypto";import{URL as ze}from"node:url";var $={linkSent:"If a matching account was found, a magic link has been sent",revokedSuccess:"All other sessions revoked successfully",logoutSuccess:"Logged out successfully"},W=class{config;email;storage;jwtService;constructor(e,t,r){this.config=e,this.email=t||new oe,this.storage=r||new ae,this.jwtService=new ce(e.jwtSecret),this.checkIfUsingDefaultCredentialsInProduction()}checkIfUsingDefaultCredentialsInProduction(){process.env.NODE_ENV==="production"&&this.config.jwtSecret===w().auth.jwtSecret&&(console.error("WARNING: Using default secrets in production environment!"),process.exit(1))}generateToken(e){let t=Date.now().toString(),r=S.randomBytes(32).toString("hex");return S.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 S.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 ze(this.config.appUrl),`${this.config.appUrl}/verify?token=${encodeURIComponent(t)}&email=${encodeURIComponent(r)}`}catch{throw new Error("Invalid base URL configuration")}}async createMagicLink(e){let{email:t,ip:r}=e;if(!ne(t))throw new Error("Valid email required");try{let s=this.generateToken(t),n=`magic_link:${s}`,i={email:t,ipAddress:r||"unknown",createdAt:Date.now()};await this.storage.set(n,JSON.stringify(i),this.config.magicLinkExpirySeconds);let o=await this.storage.findKeys("magic_link:*");console.log("existingTokens",o);for(let l of o){if(l===n)continue;let u=await this.storage.get(l);if(u)try{JSON.parse(u).email===t&&await this.storage.delete(l)}catch{}}let a=this.generateMagicLinkUrl({token:s,email:t}),c=Math.ceil(this.config.magicLinkExpirySeconds/60);return await this.email.sendMail({from:process.env.EMAIL_FROM||w().email.user,to:t,subject:process.env.EMAIL_SUBJECT||w().email.emailSubject,text:_.getText(a,c),html:_.getHtml(a,c)}),{message:$.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}`,n=await this.storage.get(s);if(!n)throw new Error("Invalid or expired token");let i=JSON.parse(n);if(i.email!==r)throw new Error("Email mismatch");let o=i.username,a=i.role;await this.storage.delete(s);let c=S.randomBytes(16).toString("hex"),l=this.generateRefreshToken(),u={sub:r,username:o,role:a,jti:c,lastLogin:i.createdAt,metadata:{ip:i.ipAddress},exp:Math.floor(Date.now()/1e3)+60*60*24},h=this.jwtService.sign(u,{exp:this.config.jwtExpirySeconds});return await this.trackSession(r,l,{...i,tokenId:c,createdAt:Date.now()}),{accessToken:h,refreshToken:l,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 n=r.username,i=r.role,o=S.randomBytes(16).toString("hex"),a={sub:s,username:n,role:i,jti:o,lastLogin:r.lastLogin||r.createdAt,metadata:{ip:r.ipAddress}},c=this.jwtService.sign(a,{exp:this.config.jwtExpirySeconds});return r.lastUsed=Date.now(),await this.storage.set(`refresh:${e}`,JSON.stringify(r),this.config.refreshTokenExpirySeconds),{accessToken:c,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:$.logoutSuccess};let s=JSON.parse(t).email;if(!s)throw new Error("Invalid refresh token data");await this.storage.delete(`refresh:${e}`);let n=`sessions:${s}`;return await this.storage.removeFromCollection(n,e),{message:$.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 a=>{try{let c=await this.storage.get(`refresh:${a}`);if(!c)return await this.storage.removeFromCollection(s,a),null;let l=JSON.parse(c);return{id:`${a.substring(0,8)}...`,createdAt:l.createdAt||0,lastLogin:l.lastLogin||l.createdAt||0,lastUsed:l.lastUsed||l.createdAt||0,metadata:{ip:l.ipAddress},isCurrentSession:a===r}}catch{return await this.storage.removeFromCollection(s,a),null}}),o=(await Promise.all(i)).filter(Boolean);return o.sort((a,c)=>c.createdAt-a.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}`,n=await this.storage.getCollection(s);for(let i of n)r&&i===r||await this.storage.delete(`refresh:${i}`);return await this.storage.delete(s),r&&await this.storage.get(`refresh:${r}`)&&await this.storage.addToCollection(s,r,this.config.refreshTokenExpirySeconds),{message:$.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 n=this.verify(s);e.user={email:n.sub},t()}catch{throw new Error("Invalid or expired token")}}catch(r){t(r)}}};var z=class extends Error{constructor(e){super(e),this.name="ValidationError",this.message=e||"Validation did not pass",this.cause={statusCode:400}}};import{existsSync as Ke,readFileSync as qe}from"node:fs";var y=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("."),n=e;for(let o=0;o<s.length-1;o++){let a=s[o];!(a in n)||n[a]===null?n[a]={}:typeof n[a]!="object"&&(n[a]={}),n=n[a]}let i=s[s.length-1];n[i]=r}getValueAtPath(e,t){let r=t.split("."),s=e;for(let n of r){if(s==null)return;s=s[n]}return s}createConfig(e,t=[],r={}){let s={};for(let a of this.options)a.defaultValue!==void 0&&this.setValueAtPath(s,a.path,a.defaultValue);let n={};if(e&&Ke(e))try{let a=qe(e,"utf8");n=JSON.parse(a),console.log(`Loaded configuration from ${e}`)}catch(a){console.error(`Error reading config file: ${a instanceof Error?a.message:String(a)}`)}let i=this.parseCliArgs(t),o=this.deepMerge({},s);return o=this.deepMerge(o,n),o=this.deepMerge(o,r),o=this.deepMerge(o,i),o}parseCliArgs(e){let t={},r=e[0]?.endsWith("node")||e[0]?.endsWith("node.exe")?2:0;for(;r<e.length;){let s=e[r++],n=this.options.find(i=>i.flag===s);if(n)if(n.isFlag)this.setValueAtPath(t,n.path,!0);else if(r<e.length&&!e[r].startsWith("-")){let i=e[r++];if(n.parser)try{i=n.parser(i)}catch(o){console.error(`Error parsing value for ${n.flag}: ${o instanceof Error?o.message:String(o)}`);continue}if(n.validator){let o=n.validator(i);if(o!==!0&&typeof o=="string"){console.error(`Invalid value for ${n.flag}: ${o}`);continue}if(o===!1){console.error(`Invalid value for ${n.flag}`);continue}}this.setValueAtPath(t,n.path,i)}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 z(e.message);if(typeof r=="string")throw new z(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`)}}};var p=()=>Date.now(),le=(e,t)=>t==="D"||e.length===0||e.join(" ")==="null"?null:ue(e),ue=e=>{try{return JSON.parse(e.join(" "))}catch{return}};function he(e){return e==="true"||e===!0}function de(e){let t=(o,a)=>{if(o){if(a==="json")return ue(o)||o;if(a==="number")return Number.parseInt(o,10)}},r=t(e?.filter,"json"),s=t(e?.sort,"json"),n=t(e?.limit,"number"),i=t(e?.offset,"number");if(!(!r&&!s&&!n&&!i))return{filter:r,sort:s,limit:n,offset:i}}var fe=()=>({port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,sslCert:"",sslKey:"",sslCa:"",debug:he(process.env.DEBUG)||!1,rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"]});var L=class extends Error{constructor(e){super(),this.name="NotFoundError",this.message=e||"Resource not found",this.cause={statusCode:404}}},b=class extends Error{constructor(e){super(),this.name="CheckpointError",this.message=e,this.cause={statusCode:500}}};import{existsSync as K}from"node:fs";import{readFile as me,unlink as Ge,writeFile as pe}from"node:fs/promises";var ge=class{checkpointInterval;defaultCheckpointIntervalMs=10*1e3;isCheckpointing=!1;lastCheckpointTime=0;walFile;checkpointTimer=null;wal;table;constructor(e){let{table:t,wal:r,walFile:s,checkpointIntervalMs:n}=e;this.defaultCheckpointIntervalMs=n||this.defaultCheckpointIntervalMs,this.table=t,this.wal=r,this.walFile=s,this.checkpointInterval=n,this.lastCheckpointTime=p()}async start(){let e=`${this.walFile}.checkpoint`;if(K(e)){console.log("Incomplete checkpoint detected, running recovery...");try{let t=await me(e,"utf8");console.log(`Incomplete checkpoint from: ${new Date(Number.parseInt(t))}`),await this.checkpoint(!0)}catch(t){throw new L(`Error reading checkpoint file: ${t}`)}}this.checkpointTimer=setInterval(async()=>{try{await this.checkpoint()}catch(t){throw new b(`Checkpoint interval failed: ${t}`)}},this.checkpointInterval)}stop(){this.checkpointTimer&&(clearInterval(this.checkpointTimer),this.checkpointTimer=null)}async checkpoint(e=!1){if(this.isCheckpointing)return;let t=p();if(!(!e&&t-this.lastCheckpointTime<this.checkpointInterval)){this.isCheckpointing=!0;try{await this.wal.flushWAL();let r=`${this.walFile}.checkpoint`;await pe(r,t.toString(),"utf8");let s=await this.getTablesFromWAL();await this.persistTables(s);try{await pe(this.walFile,"","utf8"),process.env.MIKRODB_DEBUG==="true"&&console.log("WAL truncated successfully")}catch(n){throw new b(`Failed to truncate WAL: ${n}`)}K(r)&&await Ge(r),this.wal.clearPositions(),this.lastCheckpointTime=t,process.env.MIKRODB_DEBUG==="true"&&console.log("Checkpoint complete")}catch(r){throw new b(`Checkpoint failed: ${r}`)}finally{this.isCheckpointing=!1}}}async getTablesFromWAL(){let e=new Set;if(!K(this.walFile))return e;try{let t=await me(this.walFile,"utf8");if(!t.trim())return e;let r=t.trim().split(`
    `);for(let s of r){if(!s.trim())continue;let n=s.split(" ");n.length>=3&&e.add(n[2])}}catch(t){throw new b(`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 b(`Failed to checkpoint table "${r}": ${s.message}`)}});await Promise.all(t)}};import{existsSync as ye,statSync as q,writeFileSync as Je}from"node:fs";import{appendFile as Xe,readFile as we}from"node:fs/promises";var be=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;constructor(e,t){this.walFile=e,this.walInterval=t,this.start()}setCheckpointCallback(e){this.checkpointCallback=e}start(){ye(this.walFile)||Je(this.walFile,"","utf-8"),setInterval(async()=>{try{this.walBuffer.length>0&&await this.flushWAL()}catch(e){console.error("WAL flush interval failed:",e)}},this.walInterval)}checkWalFileExists(){if(!ye(this.walFile))throw new L(`WAL file "${this.walFile}" does not exist`)}async loadWAL(e){this.checkWalFileExists();let t=[];if((q(this.walFile)?.size||0)===0)return t;try{let n=(await we(this.walFile,"utf8")).trim().split(`
    `),i=p();for(let o=0;o<n.length;o++){if(!n[o].trim())continue;let c=this.lastProcessedEntryCount.get(e||"")||0;if(e&&o<c)continue;let[l,u,h,d,m,M,...Pe]=n[o].split(" ");if(e&&h!==e)continue;let De=Number(d.split(":")[1]),R=m==="0"?null:Number(m.split(":")[1]);if(R&&R<i)continue;let Q=le(Pe,u);Q!==void 0&&(e&&this.lastProcessedEntryCount.set(e,o+1),u==="W"?t.push({operation:"W",tableName:h,key:M,data:{value:Q,version:De,timestamp:i,expiration:R}}):u==="D"&&t.push({operation:"D",tableName:h,key:M}))}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 we(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 n=s;n<r.length;n++){let i=r[n];if(!i.trim())continue;let o=i.split(" ");if(o.length>=3&&o[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 Xe(this.walFile,e.join(""),"utf8");let t=q(this.walFile);t.size>this.maxWalSizeBeforeCheckpoint&&(process.env.MIKRODB_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,n,i=0){this.checkWalFileExists();let a=`${p()} ${t} ${e} v:${n} x:${i} ${r} ${JSON.stringify(s)}
    `;this.walBuffer.push(a),this.walBuffer.length>=this.maxWalBufferEntries&&await this.flushWAL(),this.walBuffer.reduce((u,h)=>u+h.length,0)>=this.maxWalBufferSize&&await this.flushWAL();let l=q(this.walFile);l.size>this.maxWalSizeBeforeCheckpoint&&(process.env.MIKRODB_DEBUG==="true"&&console.log(`WAL size (${l.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()}};import{createCipheriv as Ye,createDecipheriv as Ze,randomBytes as Qe,scryptSync as et}from"node:crypto";var U=class{algo="aes-256-gcm";KEY_LENGTH=32;IV_LENGTH=12;generateKey(e,t){return et(`${t}#${e}`,t,this.KEY_LENGTH)}generateIV(){return Qe(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=Ye(this.algo,t,r),n=Buffer.concat([s.update(e,"utf8"),s.final()]),i=s.getAuthTag();return{iv:r,encrypted:n,authTag:i}}decrypt(e,t){let{iv:r,encrypted:s,authTag:n}=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 i=Ze(this.algo,t,r);return i.setAuthTag(n),Buffer.concat([i.update(s),i.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++],n=e.subarray(t,t+s);t+=s;let i=e[t++],o=e.subarray(t,t+i);t+=i;let a=e.subarray(t);return{iv:n,authTag:o,encrypted:a}}toHex(e){return e.toString("hex")}toUtf8(e){return e.toString("utf8")}toBuffer(e){return Buffer.from(e,"hex")}};var B=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,n=p();for(let i=0;i<t&&s+26<=e.length;i++){let o=e.readUInt16LE(s);s+=2;let a=e.readUInt32LE(s);s+=4;let c=e.readUInt32LE(s);s+=4;let l=Number(e.readBigUInt64LE(s));s+=8;let u=Number(e.readBigUInt64LE(s));if(s+=8,s+o+a>e.length)break;if(u&&u<=n){s+=o+a;continue}let h=e.toString("utf8",s,s+o);s+=o;let d=e.slice(s,s+a);s+=a;let m=this.decodeValue(d);r.set(h,{value:m,version:c,timestamp:l,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(([i])=>typeof i=="string"),n=Buffer.alloc(4);n.writeUInt32LE(s.length,0),t.push(n);for(let[i,o]of s){if(i===null||typeof i!="string")continue;let a=Buffer.from(i),c=this.encodeValue(o.value),l=Buffer.alloc(2);l.writeUInt16LE(a.length,0),t.push(l);let u=Buffer.alloc(4);u.writeUInt32LE(c.length,0),t.push(u);let h=Buffer.alloc(4);h.writeUInt32LE(o.version||0,0),t.push(h);let d=Buffer.alloc(8);d.writeBigUInt64LE(BigInt(o.timestamp||0),0),t.push(d);let m=Buffer.alloc(8);m.writeBigUInt64LE(BigInt(o.expiration||0),0),t.push(m),t.push(a),t.push(c)}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"){let t=Object.keys(e),r=[],s=Buffer.alloc(5);s[0]=6,s.writeUInt32LE(t.length,1),r.push(s);for(let n of t){let i=Buffer.from(n,"utf8"),o=Buffer.alloc(2);o.writeUInt16LE(i.length,0),r.push(o),r.push(i),r.push(this.encodeValue(e[n]))}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),n=5;for(let i=0;i<r;i++){let{value:o,bytesRead:a}=this.decodeValueWithSize(e,n);s[i]=o,n+=a}return s}case 6:{let r=e.readUInt32LE(1),s={},n=5;for(let i=0;i<r;i++){let o=e.readUInt16LE(n);n+=2;let a=e.toString("utf8",n,n+o);n+=o;let{value:c,bytesRead:l}=this.decodeValueWithSize(e,n);s[a]=c,n+=l}return s}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),n=new Array(s),i=t+5;for(let o=0;o<s;o++){let a=this.decodeValueWithSize(e,i);n[o]=a.value,i+=a.bytesRead}return{value:n,bytesRead:i-t}}case 6:{let s=e.readUInt32LE(t+1),n={},i=t+5;for(let o=0;o<s;o++){let a=e.readUInt16LE(i);i+=2;let c=e.toString("utf8",i,i+a);i+=a;let l=this.decodeValueWithSize(e,i);n[c]=l.value,i+=l.bytesRead}return{value:n,bytesRead:i-t}}default:return console.warn(`Unknown type byte: ${r} at offset ${t}`),{value:null,bytesRead:1}}}};import{writeFile as tt}from"node:fs/promises";async function Ee(e,t,r){let s=new U,i=new B().toBinaryBuffer(t);if(!i){console.log("Buffer is empty, skipping...");return}if(r)try{let o=s.generateKey(r,"salt"),a=i.toString("binary"),c=s.generateIV(),l=s.encrypt(a,o,c);i=s.serialize(l)}catch(o){console.error("Encryption failed:",o)}await tt(e,i)}var ve=class{cacheLimit;tableAccessTimes=new Map;constructor(e={}){this.cacheLimit=e.cacheLimit??20}trackTableAccess(e){this.tableAccessTimes.set(e,p())}findTablesForEviction(e){if(e<=this.cacheLimit)return[];let t=e-this.cacheLimit,r=Array.from(this.tableAccessTimes.entries()).sort((s,n)=>s[1]-n[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=p(),r=[];for(let[s,n]of e.entries())n.x&&n.x<t&&r.push([s,n]);return r}clear(){this.tableAccessTimes.clear()}};var Ce=class{async query(e,t,r=50){let s=new Map;for(let[n,i]of e.entries())if((!t||(typeof t=="function"?t(i.value):this.evaluateFilter(i.value,t)))&&(s.set(n,i.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 n=new RegExp(s);return typeof e=="string"&&n.test(e)}catch(n){return console.error("Invalid regex pattern:",n),!1}case"contains":return Array.isArray(e)&&e.includes(s);case"containsAll":return Array.isArray(e)&&Array.isArray(s)&&s.every(n=>e.includes(n));case"containsAny":return Array.isArray(e)&&Array.isArray(s)&&s.some(n=>e.includes(n));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 n=r.split("."),i=e;for(let o of n)if(i=i?.[o],i==null)return!1;if(!this.evaluateCondition(i,s))return!1}else if(s&&typeof s=="object"&&!("operator"in s)){let n=e[r];if(n==null||!this.evaluateFilter(n,s))return!1}else{let n=e[r];if(!this.evaluateCondition(n,s))return!1}return!0}};import{existsSync as ke,mkdirSync as st}from"node:fs";import{readFile as it,writeFile as nt}from"node:fs/promises";import{join as J}from"node:path";import{EventEmitter as rt}from"node:events";var G=class{emitter;targets={};options;constructor(e){this.emitter=new rt,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(n=>{s.events.includes(n)||s.events.push(n)}),!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=(o,a,c)=>({target:o,event:a,error:c}),n=Object.values(this.targets).filter(o=>o.events.includes(e)||o.events.includes("*"));n.filter(o=>!o.url).forEach(o=>{try{this.emitter.emit(e,t)}catch(a){let c=a instanceof Error?a:new Error(String(a));r.errors.push({target:o.name,event:e,error:c}),this.options.errorHandler(c,e,t),r.success=!1}});let i=n.filter(o=>o.url);if(i.length>0){let o=i.map(async a=>{try{let c=await fetch(a.url,{method:"POST",headers:{"Content-Type":"application/json",...a.headers},body:JSON.stringify({eventName:e,data:t})});if(!c.ok){let l=`HTTP error! Status: ${c.status}: ${c.statusText}`,u=new Error(l);r.errors.push(s(a.name,e,u)),this.options.errorHandler(u,e,t),r.success=!1}}catch(c){let l=c instanceof Error?c:new Error(String(c));r.errors.push(s(a.name,e,l)),this.options.errorHandler(l,e,t),r.success=!1}});await Promise.allSettled(o)}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",n=>s+=n.toString()),e.on("end",async()=>{try{await this.handleIncomingEvent(s),t.statusCode=202,t.end()}catch(n){t.statusCode=400,t.end(JSON.stringify({error:"Invalid event format"})),r&&r(n)}})}}}};var ot=new G,Se=class{databaseDirectory;walFile;activeTable=null;data=new Map;writeBuffer=[];maxWriteOpsBeforeFlush=Number.parseInt(process.env.MAX_WRITE_OPS_BEFORE_FLUSH||"100");encryptionKey;cache;encryption;persistence;query;wal;constructor(e){let{databaseDirectory:t,walFileName:r,walInterval:s}=e;this.databaseDirectory=t,this.walFile=J(this.databaseDirectory,r),this.encryptionKey=e.encryptionKey?e.encryptionKey:null,ke(this.databaseDirectory)||st(this.databaseDirectory),this.cache=new ve,this.encryption=new U,this.persistence=new B,this.query=new Ce,this.wal=new be(this.walFile,s)}async start(){await this.applyWALEntries()}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 n=t.filter(i=>i.tableName===s);e&&!this.hasTable(s)?await this.loadTable(s):this.createTable(s);for(let i of n)i.operation==="W"&&i.data?this.setItem(s,i.key,i.data):i.operation==="D"&&this.deleteItem(s,i.key)}}async loadTable(e){let t=J(this.databaseDirectory,e);if(this.hasTable(e))return;if(!ke(t)){this.createTable(e);return}let r=await it(t),s=r;if(this.encryptionKey&&r.length>0&&r[0]===1)try{let i=this.encryption.deserialize(r),o=this.encryption.generateKey(this.encryptionKey,"salt"),a=this.encryption.decrypt(i,o);s=Buffer.from(a,"binary")}catch(i){console.error(`Failed to decrypt ${e}:`,i)}let n=this.persistence.readTableFromBinaryBuffer(s);this.data.set(e,n),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 n=this.getTable(t),i=await this.query.query(n,s.filter,s.limit);if(s.sort&&(i=i.sort(s.sort)),s.offset!=null||s.limit!=null){let o=s.offset||0,a=s.limit?o+s.limit:void 0;i=i.slice(o,a)}return i}async write(e,t={}){let{concurrencyLimit:r=10,flushImmediately:s=!1}=t,n=Array.isArray(e)?e:[e],i=n.length,o=0;for(;o<i;){let a=n.slice(o,o+r),c=a.map(u=>this.writeItem(u,!1));if((await Promise.all(c)).includes(!1))return!1;o+=a.length,this.writeBuffer.length>=this.maxWriteOpsBeforeFlush&&await this.flushWrites()}return(s||i>0)&&await this.flush(),!0}async writeItem(e,t=!1){let{tableName:r,key:s,value:n,expectedVersion:i=null,expiration:o=0}=e;await this.setActiveTable(r);let{success:a,newVersion:c}=this.getItemVersion(r,s,i);return a?(await this.wal.appendToWAL(r,"W",s,n,c,o),this.setItem(r,s,{value:n,v:c,t:p(),x:o}),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:n,currentVersion:i,expiration:o}=this.getItemVersion(e,t,r);return n?(await this.wal.appendToWAL(e,"D",t,null,i,o),this.deleteItem(e,t),s&&await this.flush(),!0):!1}getItemVersion(e,t,r){let s=this.getItem(e,t),n=s?s.version:0,i=n+1,o=s&&s.expiration||0,a=!0;return r!==null&&n!==r&&(console.log(`Version mismatch for ${e}:${t}. Expected ${r}, found ${n}`),a=!1),{success:a,currentRecord:s,currentVersion:n,newVersion:i,expiration:o}}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}deleteTable(e){this.trackTableAccess(e),this.data.delete(e)}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)}deleteItem(e,t){this.data.get(e)?.delete(t)}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 n=JSON.parse(s);e.has(n.tableName)||e.set(n.tableName,new Map),e.get(n.tableName).set(n.key,n.record);let{success:o,errors:a}=await ot.emit("write.flushed",{operation:n.key,record:n.record});o||console.error(a)}let r=Array.from(e.entries()).map(async([s])=>{let n=this.getTable(s),i=J(this.databaseDirectory,s);await Ee(i,n,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,n]of r)await this.wal.appendToWAL(e,"D",s,null,n.v,n.x),t.delete(s)}}async dump(e){e&&await this.setActiveTable(e);let t=this.getAll(this.activeTable);await nt(`${this.databaseDirectory}/${this.activeTable}_dump.json`,JSON.stringify(t),"utf8")}getWAL(){return this.wal}getPersistence(){return this.persistence}};import{join as at}from"node:path";var x=class{table;checkpoint;constructor(e){let t=fe(),r=new y({configFilePath:"mikrodb.config.json",args:process.argv,options:[{flag:"--port",path:"port",defaultValue:t.port},{flag:"--host",path:"host",defaultValue:t.host},{flag:"--db",path:"dbName",defaultValue:"mikrodb"},{flag:"--https",path:"useHttps",isFlag:!0,defaultValue:t.useHttps},{flag:"--cert",path:"sslCert",defaultValue:t.sslCert},{flag:"--key",path:"sslKey",defaultValue:t.sslKey},{flag:"--ca",path:"sslCa",defaultValue:t.sslCa},{flag:"--debug",path:"debug",isFlag:!0,defaultValue:t.debug}]}).get();r.debug&&console.log("Using configuration:",r);let{databaseDirectory:s="mikrodb",walFileName:n="wal.log",walInterval:i=1e3,encryptionKey:o}=e;e.debug&&(process.env.MIKRODB_DEBUG="true"),this.table=new Se({databaseDirectory:s,walFileName:n,walInterval:i,encryptionKey:o});let a=this.table.getWAL();this.table.getWAL().setCheckpointCallback(()=>this.checkpoint.checkpoint(!0)),this.checkpoint=new ge({table:this.table,wal:a,walFile:at(s,n),checkpointIntervalMs:2e3}),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;return await this.table.delete(t,r)}async close(){await this.flush()}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 Te=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 ct}from"node:url";var xe=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 n=s.pattern.exec(t);if(!n)continue;let i={};return s.paramNames.forEach((o,a)=>{i[o]=n[a+1]||""}),{route:r,params:i}}return null}createPathPattern(e){let t=[],r=e.replace(/\/:[^/]+/g,s=>{let n=s.slice(2);return t.push(n),"/([^/]+)"}).replace(/\/$/,"/?");return{pattern:new RegExp(`^${r}$`),paramNames:t}}async handle(e,t){let r=e.method||"GET",s=new ct(e.url||"/",`http://${e.headers.host}`),n=s.pathname,i=this.match(r,n);if(!i)return null;let{route:o,params:a}=i,c={};s.searchParams.forEach((h,d)=>{c[d]=h});let l={req:e,res:t,params:a,query:c,body:e.body||{},headers:e.headers,path:n,state:{},raw:()=>t,binary:(h,d="application/octet-stream",m=200)=>({statusCode:m,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,m="application/octet-stream")=>({statusCode:h,body:d,headers:{"Content-Type":m,"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,m=302)=>({statusCode:m,body:null,headers:{Location:d}}),status:d=>this.status(d)}}},u=[...this.globalMiddlewares,...o.middlewares];return this.executeMiddlewareChain(l,u,o.handler)}async executeMiddlewareChain(e,t,r){let s=0,n=async()=>{if(s<t.length){let i=t[s++];return i(e,n)}return r(e)};return n()}};var P=()=>({port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",debug:lt(process.env.DEBUG)||!1,rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"]});function lt(e){return e==="true"||e===!0}var g=P(),Ae=e=>({configFilePath:"mikroserve.config.json",args:process.argv,options:[{flag:"--port",path:"port",defaultValue:g.port},{flag:"--host",path:"host",defaultValue:g.host},{flag:"--https",path:"useHttps",defaultValue:g.useHttps,isFlag:!0},{flag:"--http2",path:"useHttp2",defaultValue:g.useHttp2,isFlag:!0},{flag:"--cert",path:"sslCert",defaultValue:g.sslCert},{flag:"--key",path:"sslKey",defaultValue:g.sslKey},{flag:"--ca",path:"sslCa",defaultValue:g.sslCa},{flag:"--ratelimit",path:"rateLimit.enabled",defaultValue:g.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"rateLimit.requestsPerMinute",defaultValue:g.rateLimit.requestsPerMinute},{flag:"--allowed",path:"allowedDomains",defaultValue:g.allowedDomains,parser:T.array},{flag:"--debug",path:"debug",defaultValue:g.debug,isFlag:!0}],config:e});import{readFileSync as E}from"node:fs";import X from"node:http";import ut from"node:http2";import ht from"node:https";var A=class{config;rateLimiter;router;constructor(e){let t=new y(Ae(e||{})).get();t.debug&&console.log("Using configuration:",t),this.config=t,this.router=new xe;let r=t.rateLimit.requestsPerMinute||P().rateLimit.requestsPerMinute;this.rateLimiter=new Te(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(),n=this.config.useHttps?"https":"http";console.log(`MikroServe running at ${n}://${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:E(this.config.sslKey),cert:E(this.config.sslCert),...this.config.sslCa?{ca:E(this.config.sslCa)}:{}};return ut.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:E(this.config.sslKey),cert:E(this.config.sslCert),...this.config.sslCa?{ca:E(this.config.sslCa)}:{}};return ht.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 X.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",n=e.url||"/unknown",i=this.config.debug;try{if(this.setCorsHeaders(t,e),this.setSecurityHeaders(t,this.config.useHttps),i&&console.log(`${s} ${n}`),e.method==="OPTIONS"){if(t instanceof X.ServerResponse)t.statusCode=204,t.end();else{let a=t;a.writeHead(204),a.end()}return}try{e.body=await this.parseBody(e)}catch(a){return i&&console.error("Body parsing error:",a.message),this.respond(t,{statusCode:400,body:{error:"Bad Request",message:a.message}})}let o=await this.router.handle(e,t);return o?o._handled?void 0:this.respond(t,o):this.respond(t,{statusCode:404,body:{error:"Not Found",message:"The requested endpoint does not exist"}})}catch(o){return console.error("Server error:",o),this.respond(t,{statusCode:500,body:{error:"Internal Server Error",message:i?o.message:"An unexpected error occurred"}})}finally{i&&this.logDuration(r,s,n)}}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=[],n=0,i=1024*1024,o=!1,a=this.config.debug,c=e.headers["content-type"]||"";a&&console.log("Content-Type:",c),e.on("data",l=>{if(n+=l.length,a&&console.log(`Received chunk: ${l.length} bytes, total size: ${n}`),n>i&&!o){o=!0,a&&console.log(`Body size exceeded limit: ${n} > ${i}`),r(new Error("Request body too large"));return}o||s.push(l)}),e.on("end",()=>{if(!o){a&&console.log(`Request body complete: ${n} bytes`);try{if(s.length>0){let l=Buffer.concat(s).toString("utf8");if(c.includes("application/json"))try{t(JSON.parse(l))}catch(u){r(new Error(`Invalid JSON in request body: ${u.message}`))}else if(c.includes("application/x-www-form-urlencoded")){let u={};new URLSearchParams(l).forEach((h,d)=>{u[d]=h}),t(u)}else t(l)}else t({})}catch(l){r(new Error(`Invalid request body: ${l}`))}}}),e.on("error",l=>{o||r(new Error(`Error reading request body: ${l.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 X.ServerResponse)Object.entries(r).forEach(([s,n])=>{e.setHeader(s,n)});else{let s=e;Object.entries(r).forEach(([n,i])=>{s.setHeader(n,i)})}}respond(e,t){let r={...t.headers||{}};(n=>typeof n.writeHead=="function"&&typeof n.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 Ie(){let e=new x({databaseDirectory:"mikrodb-test"});await e.start();let t=new A;return t.get("/table",async r=>{let s=r.req.body;if(!s.tableName)return r.json({statusCode:400,body:"tableName is required"});let n=await e.getTableSize(s.tableName);return n||r.json({statusCode:404,body:null}),r.json({statusCode:200,body:n})}),t.post("/get",async r=>{let s=r.req.body;if(!s.tableName)return r.json({statusCode:400,body:"tableName is required"});let n={tableName:s.tableName,key:s.key},i=de(s?.options);i&&(n.options=i);let o=await e.get(n);return o||r.json({statusCode:404,body:null}),r.json({statusCode:200,body:o})}),t.post("/write",async r=>{let s=r.req.body;if(!s.tableName||s.value===void 0)return r.json({statusCode:400,body:"tableName and value are required is required"});let n={tableName:s.tableName,key:s.key,value:s.value,expectedVersion:s.expectedVersion,expiration:s.expiration},i={concurrencyLimit:s.concurrencyLimit,flushImmediately:s.flushImmediately},o=await e.write(n,i);return r.json({statusCode:200,body:o})}),t.delete("/delete",async r=>{let s=r.req.query;if(!s.tableName||!s.key)return r.json({statusCode:400,body:"tableName and key are required"});let n={tableName:s.tableName,key:s.key},i=await e.delete(n);return r.json({statusCode:200,body:i})}),t.start(),t}async function dt(){let t=process.argv[1]?.includes("node_modules/.bin/mikrodb"),r=process.argv[1]?.includes("mikrodb/src/index.ts")&&(process.argv[2]||"")==="--force";if(t||r){console.log("\u{1F5C2}\uFE0F Welcome to MikroDB! \u2728");try{Ie()}catch(s){console.error(s)}}}dt();import I from"node:crypto";import{EventEmitter as ft}from"node:events";var v=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,n)=>s.createdAt-n.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 n=await this.getMessageById(t);if(!n)return null;n.reactions||(n.reactions={});let i=n.reactions[r]||[];return i.includes(s)||(n.reactions[r]=[...i,s],await this.updateMessage(n)),n}async removeReaction(t,r,s){let n=await this.getMessageById(t);return n?(!n.reactions||!n.reactions[r]||(n.reactions[r]=n.reactions[r].filter(i=>i!==s),await this.updateMessage(n)),n):null}async getServerSettings(){return this.db.get("server:settings")}async updateServerSettings(t){await this.db.set("server:settings",t)}};var D=class extends v{db;constructor(){super(),this.db=new Y}},Y=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 N=class{config;db;eventEmitter;generalChannelName="General";constructor(t,r){this.config=t,this.db=r||new D,this.eventEmitter=new ft,this.initialize()}async initialize(){let t=this.config.initialUser.id,r=this.config.initialUser.userName,s=this.config.initialUser.email;await this.db.getUserById(t)||await this.db.createUser({id:t||I.randomUUID(),userName:r||s.split("@")[0],email:s,isAdmin:!0,createdAt:Date.now()}),await this.db.getChannelByName(this.generalChannelName)||await this.db.createChannel({id:I.randomUUID(),name:this.generalChannelName,createdAt:Date.now(),createdBy:this.config.initialUser.id}),this.scheduleMessageCleanup()}scheduleMessageCleanup(){setInterval(async()=>{let s=await this.db.listChannels(),n=24*60*60*1e3,i=Date.now()-this.config.messageRetentionDays*n;for(let o of s){let a=await this.db.listMessagesByChannel(o.id);for(let c of a)c.createdAt<i&&(await this.db.deleteMessage(c.id),this.emitEvent({type:"DELETE_MESSAGE",payload:{id:c.id,channelId:o.id}}))}},36e5)}async getServerSettings(){return await this.db.getServerSettings()}async updateServerSettings(t){return await this.db.updateServerSettings(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){let n=await this.db.getUserById(r);if(!n)throw new Error("User not found");if(await this.db.getUserByEmail(t))throw new Error("User with this email already exists");if(s&&!n.isAdmin)throw new Error("Only administrators can add admin users");let o={id:I.randomUUID(),userName:t.split("@")[0],email:t,isAdmin:s,createdAt:Date.now(),addedBy:r};return await this.db.createUser(o),this.emitEvent({type:"NEW_USER",payload:{id:o.id,userName:o.userName,email:o.email}}),o.id}async removeUser(t,r){let s=await this.db.getUserById(t);if(!s)throw new Error("User not found");let n=await this.db.getUserById(r);if(!n)throw new Error("Requester not found");if(!n.isAdmin)throw new Error("Only administrators can remove users");if(s.isAdmin&&(await this.db.listUsers()).filter(o=>o.isAdmin).length<=1)throw new Error("Cannot remove the last administrator");await this.db.deleteUser(t),this.emitEvent({type:"REMOVE_USER",payload:{id:t,email:s.email}})}async exitUser(t){let r=await this.db.getUserById(t);if(!r)throw new Error("User not found");await this.db.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 n={id:I.randomUUID(),name:t,createdAt:Date.now(),createdBy:r};return await this.db.createChannel(n),this.emitEvent({type:"NEW_CHANNEL",payload:n}),n}async updateChannel(t,r,s){let n=await this.db.getChannelById(t);if(!n)throw new Error("Channel not found");let i=await this.db.getUserById(s);if(!i)throw new Error("User not found");if(n.createdBy!==s&&!i.isAdmin)throw new Error("You can only edit channels you created");let o=await this.db.getChannelById(t);if(o&&o?.name===this.generalChannelName)throw new Error(`The ${this.generalChannelName} channel cannot be renamed`);if(o&&o?.id!==t)throw new Error(`Channel with name "${r}" already exists`);return n.name=r,n.updatedAt=Date.now(),await this.db.updateChannel(n),this.emitEvent({type:"UPDATE_CHANNEL",payload:n}),n}async deleteChannel(t,r){let s=await this.db.getChannelById(t);if(!s)throw new Error("Channel not found");let n=await this.db.getUserById(r);if(!n)throw new Error("User not found");if(s.createdBy!==r&&!n.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 i=await this.db.listMessagesByChannel(t);for(let o of i)await this.db.deleteMessage(o.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,n=[]){let i=await this.db.getUserById(r);if(!i)throw new Error("Author not found");if(!await this.db.getChannelById(s))throw new Error("Channel not found");let a=Date.now(),c={id:I.randomUUID(),author:{id:r,userName:i.userName},images:n,content:t,channelId:s,createdAt:a,updatedAt:a,reactions:{}};await this.db.createMessage(c);let l=await this.db.listMessagesByChannel(s);if(l.length>this.config.maxMessagesPerChannel){let u=l[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:c}),c}async updateMessage(t,r,s,n){let i=await this.db.getMessageById(t);if(!i)throw new Error("Message not found");if(i.author.id!==r)throw new Error("You can only edit your own messages");return i.content=s,n&&(i.images=n),i.updatedAt=Date.now(),await this.db.updateMessage(i),this.emitEvent({type:"UPDATE_MESSAGE",payload:i}),i}async deleteMessage(t,r){let s=await this.db.getMessageById(t);if(!s)throw new Error("Message not found");let n=await this.db.getUserById(r);if(!n)throw new Error("User not found");if(s.author.id!==r&&!n.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.db.getUserById(r))throw new Error("User not found");if(!await this.db.getMessageById(t))throw new Error("Message not found");let o=await this.db.addReaction(t,r,s);if(!o)throw new Error("Failed to add reaction");return this.emitEvent({type:"NEW_REACTION",payload:{messageId:t,userId:r,reaction:s}}),o}async removeReaction(t,r,s){if(!await this.db.getUserById(r))throw new Error("User not found");if(!await this.db.getMessageById(t))throw new Error("Message not found");let o=await this.db.removeReaction(t,r,s);if(!o)throw new Error("Failed to remove reaction");return this.emitEvent({type:"DELETE_REACTION",payload:{messageId:t,userId:r,reaction:s}}),o}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 mt}from"node:crypto";import{existsSync as Me,mkdirSync as pt,readFileSync as gt,writeFileSync as yt}from"node:fs";import{join as $e}from"node:path";var wt=2,bt=["jpg","jpeg","png","webp","svg"];async function Le(e,t,r){let s=new A(e.server);console.log("Dev mode?",e.devMode),e.devMode&&s.post("/auth/dev-login",async i=>{if(!i.body.email)return i.json({error:"Email is required"},400);let{email:o}=i.body;try{let a=await r.listUsers(),c=a.find(u=>u.email===o);c||(c={id:crypto.randomUUID(),userName:o.split("@")[0],email:o,isAdmin:a.length===0,createdAt:Date.now()},await r.createUser(c));let l=t.generateJsonWebToken(c);return i.json({user:c,token:l},200)}catch(a){return console.error("Dev login error:",a),i.json({error:a.message},400)}}),s.post("/auth/login",async i=>{if(!i.body.email)return i.json({error:"Email is required"},400);let{email:o}=i.body,a=await t.createMagicLink({email:o,ip:i.req.socket.remoteAddress});return a?i.json({success:!0,message:a.message},200):i.json({error:"Failed to create magic link"},400)}),s.post("/auth/logout",n,async i=>{let a=i.body.refreshToken;if(!a)return i.json({error:"Missing refresh token"},400);let c=await t.logout(a);return i.json(c,200)}),s.get("/auth/me",n,async i=>i.json({user:i.state.user},200)),s.post("/auth/verify",async i=>{let o=i.body,c=(i.headers.authorization||"").split(" ")[1];if(!c)return i.json({error:"Token is required"},400);let l;try{if(l=await t.verifyToken({email:o.email,token:c}),!l)return i.json({error:"Invalid token"},400)}catch{return i.json({error:"Invalid token"},400)}return i.json(l,200)}),s.post("/auth/refresh",async i=>{let a=i.body.refreshToken,c=await t.refreshAccessToken(a);return i.json(c,200)}),s.get("/auth/sessions",n,async i=>{let o=i.headers.authorization;if(!o||!o.startsWith("Bearer "))return i.json(null,401);let a=i.body,c=o.split(" ")[1],u={email:t.verify(c).sub},h=await t.getSessions({body:a,user:u});return i.json(h,200)}),s.delete("/auth/sessions",n,async i=>{let o=i.headers.authorization;if(!o||!o.startsWith("Bearer "))return i.json(null,401);let a=i.body,c=o.split(" ")[1],u={email:t.verify(c).sub},h=await t.revokeSessions({body:a,user:u});return i.json(h,200)}),s.post("/users/add",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let{email:a,role:c}=i.body;if(!a)return i.json({error:"Email is required"},400);try{if(c==="admin"&&!o.isAdmin)return i.json({error:"Only administrators can add admin users"},403);let l=await r.addUser(a,o.id,c==="admin");return i.json({success:!0,userId:l},200)}catch(l){return i.json({error:l.message},400)}}),s.get("/users",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let a=await r.listUsers();return i.json({users:a},200)}catch(a){return i.json({error:a.message},400)}}),s.delete("/users/:id",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.id;if(a===o.id)return i.json({error:"You cannot remove your own account"},400);try{return await r.removeUser(a,o.id),i.json({success:!0},200)}catch(c){return i.json({error:c.message},400)}}),s.post("/users/exit",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);try{return o.isAdmin&&(await r.listUsers()).filter(c=>c.isAdmin).length<=1?i.json({error:"Cannot exit as the last administrator"},400):(await r.exitUser(o.id),i.json({success:!0,message:"You have exited the server"},200))}catch(a){return i.json({error:a.message},400)}}),s.get("/channels",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);let a=await r.listChannels();return i.json({channels:a},200)}),s.post("/channels",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let{name:a}=i.body;if(!a)return i.json({error:"Channel name is required"},400);try{let c=await r.createChannel(a,o.id);return i.json({channel:c},200)}catch(c){return i.json({error:c.message},400)}}),s.get("/channels/:channelId/messages",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);let a=i.params.channelId;try{let c=await r.getMessagesByChannel(a),l=await Promise.all(c.map(async u=>{let h=await r.getUserById(u.author.id);return{...u,author:{id:h?.id,userName:h?.userName||"Unknown User"}}}));return i.json({messages:l},200)}catch(c){return i.json({error:c.message},400)}}),s.post("/channels/:channelId/messages",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.channelId,{content:c}=i.body;if(!c)return i.json({error:"Message content is required"},400);try{let l=await r.createMessage(c,o.id,a);return i.json({message:l},200)}catch(l){return i.json({error:l.message},400)}}),s.put("/channels/:channelId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.channelId,{name:c}=i.body;if(!c)return i.json({error:"Channel name is required"},400);try{let l=await r.updateChannel(a,c,o.id);return i.json({channel:l},200)}catch(l){return i.json({error:l.message},400)}}),s.delete("/channels/:channelId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.channelId;try{return await r.deleteChannel(a,o.id),i.json({success:!0},200)}catch(c){return i.json({error:c.message},400)}}),s.put("/messages/:messageId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId,c=i.body?.content||"TODO",l=i.body?.images;if(!c)return i.json({error:"Message content is required"},400);try{let u=await r.updateMessage(a,o.id,c,l);return i.json({message:u},200)}catch(u){return i.json({error:u.message},400)}}),s.delete("/messages/:messageId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId;try{return await r.deleteMessage(a,o.id),i.json({success:!0},200)}catch(c){return i.json({error:c.message},400)}}),s.post("/messages/:messageId/reactions",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId,{reaction:c}=i.body;if(!c)return i.json({error:"Reaction is required"},400);try{let l=await r.addReaction(a,o.id,c);return l?i.json({message:l},200):i.json({error:`Message with ID ${a} not found`},401)}catch(l){return console.error(`Error adding reaction to message ${a}:`,l),i.json({error:l.message},400)}}),s.delete("/messages/:messageId/reactions",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId,{reaction:c}=i.body;if(!c)return i.json({error:"Reaction is required"},400);try{let l=await r.removeReaction(a,o.id,c);return l?i.json({message:l},200):i.json({error:`Message with ID ${a} not found`},404)}catch(l){return console.error(`Error removing reaction from message ${a}:`,l),i.json({error:l.message},400)}}),s.post("/channels/:channelId/messages/image",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let{filename:a,image:c}=await i.body;if(!c)return i.json({error:"No image provided"},400);let l=a.split(".").pop();if(!l)return i.json({error:"Missing file extension"},400);if(!bt.includes(l))return i.json({error:"Unsupported file format"},400);let u=Buffer.from(c,"base64"),h=wt*1024*1024;if(u.length>h)return i.json({error:"Image too large"},400);let d=`${process.cwd()}/uploads`;Me(d)||pt(d);let m=`${Date.now()}-${mt(1).toString("hex")}.${l}`,M=$e(d,m);return yt(M,u),i.json({success:!0,filename:m})}catch(a){return console.error("Image upload error:",a),i.json({success:!1,error:a instanceof Error?a.message:"Upload failed"},500)}}),s.get("/channels/:channelId/messages/image/:filename",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let{filename:a}=i.params,c=`${process.cwd()}/uploads`,l=$e(c,a);if(!Me(l))return i.json({error:"Image not found"},404);let u=gt(l),h=a.split(".").pop()?.toLowerCase(),d="application/octet-stream";return h==="jpg"||h==="jpeg"?d="image/jpeg":h==="png"?d="image/png":h==="webp"?d="image/webp":h==="svg"&&(d="image/svg+xml"),i.binary(u,d)}catch(a){return i.json({success:!1,error:a instanceof Error?a.message:"Image fetch failed"},500)}}),s.get("/server/settings",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let a=await r.getServerSettings();return i.json(a||{name:"MikroChat"},200)}catch(a){return i.json({error:a.message},400)}}),s.put("/server/settings",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);let{name:a}=i.body;if(!a)return i.json({error:"Server name is required"},400);try{return await r.updateServerSettings({name:a}),i.json({name:a},200)}catch(c){return i.json({error:c.message},400)}}),s.get("/events",async i=>{let o=null,a=i.query.token;if(a)try{let u=t.verify(a);o=await r.getUserByEmail(u.email||u.sub)}catch(u){console.error("SSE token validation error:",u)}if(!o&&i.headers.authorization?.startsWith("Bearer ")){let u=i.headers.authorization.substring(7);try{let h=t.verify(u);o=await r.getUserByEmail(h.email||h.sub)}catch(h){console.error("SSE header validation error:",h)}}if(!o)return console.log("SSE unauthorized access attempt"),{statusCode:401,body:{error:"Unauthorized"},headers:{"Content-Type":"application/json"}};console.log(`SSE connection established for user ${o.id}`),i.res.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"});let c=setInterval(()=>{if(!i.res.writable){clearInterval(c);return}i.res.write(`: ping
    `)},3e4);i.res.write(`:
    `),i.res.write(`data: ${JSON.stringify({type:"CONNECTED",payload:{message:"SSE connection established",timestamp:new Date().toISOString(),userId:o.id}})}
    `);let l=r.subscribeToEvents(u=>{if(!i.res.writable){l();return}try{console.log(`Sending SSE event to user ${o.id}: ${u.type}`),i.res.write(`data: ${JSON.stringify(u)}
    `)}catch(h){console.error("Error sending SSE event:",h),i.res.writable&&i.res.end(),l(),clearInterval(c)}});return i.req.on("close",()=>{console.log(`SSE connection closed for user ${o.id}`),l(),clearInterval(c)}),i.req.on("error",u=>{console.error(`SSE connection error for user ${o.id}:`,u),l(),clearInterval(c),i.res.writable&&i.res.end()}),i.res.on("error",u=>{console.error(`SSE response error for user ${o.id}:`,u),l(),clearInterval(c)}),{statusCode:200,_handled:!0,body:null}});async function n(i,o){let a={error:"Unauthorized",message:"Authentication required"},c=i.headers.authorization;if(!c||!c.startsWith("Bearer "))return i.status(401).json(a);let l=c.split(" ")[1];if(!l)return i.status(401).json(a);let u=t.verify(l),h=await r.getUserByEmail(u.email||u.sub);return i.state.user=h,o()}s.start()}var j=class extends v{db;mikroDb;constructor(t){super(),this.mikroDb=t,this.db=new Z(t)}async start(){await this.mikroDb.start()}async close(){await this.mikroDb.close()}},Z=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 n=s?Date.now()+s*1e3:0;await this.db.write({tableName:this.tableName,key:t,value:r,expiration:n})}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(n=>Array.isArray(n)&&typeof n[0]=="string"&&n[0].startsWith(t)).map(n=>n[1].value)}catch(r){return console.error(`Error listing with prefix ${t}:`,r),[]}}};var Ue=()=>{let e=Et(process.env.DEBUG)||!1;return{auth:{jwtSecret:process.env.AUTH_JWT_SECRET||"your-jwt-secret",magicLinkExpirySeconds:900,jwtExpirySeconds:900,refreshTokenExpirySeconds:604800,maxActiveSessions:3,appUrl:process.env.APP_URL||"http://127.0.0.1:3000",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,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["http://127.0.0.1:8080"],devMode:!1,debug:e},chat:{initialUser:{id:process.env.INITIAL_USER_ID||crypto.randomUUID(),userName:process.env.INITIAL_USER_NAME||"",email:process.env.INITIAL_USER_EMAIL||""},messageRetentionDays:30,maxMessagesPerChannel:100}}};function Et(e){return e==="true"||e===!0}var f=Ue(),Be={configFilePath:"mikroauth.config.json",args:process.argv,options:[{flag:"--jwtSecret",path:"auth.jwtSecret",defaultValue:f.auth.jwtSecret},{flag:"--magicLinkExpirySeconds",path:"auth.magicLinkExpirySeconds",defaultValue:f.auth.magicLinkExpirySeconds},{flag:"--jwtExpirySeconds",path:"auth.jwtExpirySeconds",defaultValue:f.auth.jwtExpirySeconds},{flag:"--refreshTokenExpirySeconds",path:"auth.refreshTokenExpirySeconds",defaultValue:f.auth.refreshTokenExpirySeconds},{flag:"--maxActiveSessions",path:"auth.maxActiveSessions",defaultValue:f.auth.maxActiveSessions},{flag:"--appUrl",path:"auth.appUrl",defaultValue:f.auth.appUrl},{flag:"--debug",path:"auth.debug",isFlag:!0,defaultValue:f.auth.debug},{flag:"--emailSubject",path:"email.emailSubject",defaultValue:"Your Secure Login Link"},{flag:"--emailHost",path:"email.host",defaultValue:f.email.host},{flag:"--emailUser",path:"email.user",defaultValue:f.email.user},{flag:"--emailPassword",path:"email.password",defaultValue:f.email.password},{flag:"--emailPort",path:"email.port",defaultValue:f.email.host},{flag:"--emailSecure",path:"email.secure",isFlag:!0,defaultValue:f.email.secure},{flag:"--emailMaxRetries",path:"email.maxRetries",defaultValue:f.email.maxRetries},{flag:"--debug",path:"email.debug",isFlag:!0,defaultValue:f.email.debug},{flag:"--db",path:"storage.databaseDirectory",defaultValue:f.storage.databaseDirectory},{flag:"--encryptionKey",path:"storage.encryptionKey",defaultValue:f.storage.encryptionKey},{flag:"--debug",path:"storage.debug",defaultValue:f.storage.debug},{flag:"--port",path:"server.port",defaultValue:f.server.port},{flag:"--host",path:"server.host",defaultValue:f.server.host},{flag:"--https",path:"server.useHttps",isFlag:!0,defaultValue:f.server.useHttps},{flag:"--cert",path:"server.sslCert",defaultValue:f.server.sslCert},{flag:"--key",path:"server.sslKey",defaultValue:f.server.sslKey},{flag:"--ca",path:"server.sslCa",defaultValue:f.server.sslCa},{flag:"--ratelimit",path:"server.rateLimit.enabled",defaultValue:f.server.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"server.rateLimit.requestsPerMinute",defaultValue:f.server.rateLimit.requestsPerMinute},{flag:"--allowed",path:"server.allowedDomains",defaultValue:f.server.allowedDomains,parser:T.array},{flag:"--dev",path:"server.devMode",defaultValue:f.server.devMode,isFlag:!0},{flag:"--debug",path:"server.debug",isFlag:!0,defaultValue:f.server.debug},{flag:"--initialUserId",path:"chat.initialUser.id",defaultValue:f.chat.initialUser.id},{flag:"--initialUserUsername",path:"chat.initialUser.userName",defaultValue:f.chat.initialUser.userName},{flag:"--initialUserEmail",path:"chat.initialUser.email",defaultValue:f.chat.initialUser.email},{flag:"--messageRetentionDays",path:"chat.messageRetentionDays",defaultValue:f.chat.messageRetentionDays},{flag:"--maxMessagesPerChannel",path:"chat.maxMessagesPerChannel",defaultValue:f.chat.maxMessagesPerChannel}]};async function vt(){let e=Ct(),t=new x(e.storage);await t.start();let r=new V(t),s=new j(t),n=new O(e.email),i=new W(e.auth,n,r),o=new N(e.chat,s);await Le(e.server,i,o)}function Ct(){let e=new y({...Be,config:{email:{user:"test@starlite.systems",password:"77FUX6F2GSJSF93W",host:"smtp.protonmail.ch",port:465,secure:!0}}}).get();return e.auth.debug&&console.log("Using configuration:",e),e}vt();