tempest-express-sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -5,6 +5,7 @@ var zodToOpenapi = require('@asteasolutions/zod-to-openapi');
5
5
  var zod = require('zod');
6
6
  var tempestDbJs = require('tempest-db-js');
7
7
  var crypto = require('crypto');
8
+ var os = require('os');
8
9
  var promises = require('fs/promises');
9
10
  var path = require('path');
10
11
  var express2 = require('express');
@@ -6755,6 +6756,361 @@ var AttemptThrottle = class {
6755
6756
  }
6756
6757
  };
6757
6758
 
6759
+ // src/utils/clientIp.ts
6760
+ var UNKNOWN = "unknown";
6761
+ function getClientIp(req, options = {}) {
6762
+ if (options.trustedHeader) {
6763
+ const value = req.header(options.trustedHeader);
6764
+ if (value) return value.trim();
6765
+ }
6766
+ return req.socket?.remoteAddress ?? req.ip ?? UNKNOWN;
6767
+ }
6768
+ var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
6769
+ function base32Encode(bytes) {
6770
+ let bits = 0;
6771
+ let value = 0;
6772
+ let out = "";
6773
+ for (const byte of bytes) {
6774
+ value = value << 8 | byte;
6775
+ bits += 8;
6776
+ while (bits >= 5) {
6777
+ out += BASE32_ALPHABET[value >>> bits - 5 & 31];
6778
+ bits -= 5;
6779
+ }
6780
+ }
6781
+ if (bits > 0) out += BASE32_ALPHABET[value << 5 - bits & 31];
6782
+ return out;
6783
+ }
6784
+ function base32Decode(secret) {
6785
+ const clean = secret.toUpperCase().replace(/=+$/, "").replace(/\s/g, "");
6786
+ let bits = 0;
6787
+ let value = 0;
6788
+ const out = [];
6789
+ for (const char of clean) {
6790
+ const index = BASE32_ALPHABET.indexOf(char);
6791
+ if (index === -1) continue;
6792
+ value = value << 5 | index;
6793
+ bits += 5;
6794
+ if (bits >= 8) {
6795
+ out.push(value >>> bits - 8 & 255);
6796
+ bits -= 8;
6797
+ }
6798
+ }
6799
+ return Buffer.from(out);
6800
+ }
6801
+ function hotp(secret, counter, digits) {
6802
+ const buffer = Buffer.alloc(8);
6803
+ buffer.writeUInt32BE(Math.floor(counter / 2 ** 32), 0);
6804
+ buffer.writeUInt32BE(counter >>> 0, 4);
6805
+ const digest = crypto.createHmac("sha1", secret).update(buffer).digest();
6806
+ const offset = digest[digest.length - 1] & 15;
6807
+ const binary = (digest[offset] & 127) << 24 | (digest[offset + 1] & 255) << 16 | (digest[offset + 2] & 255) << 8 | digest[offset + 3] & 255;
6808
+ return (binary % 10 ** digits).toString().padStart(digits, "0");
6809
+ }
6810
+ var TOTPHelper = class {
6811
+ issuer;
6812
+ step;
6813
+ digits;
6814
+ /**
6815
+ * @param options - Issuer label, time step and digit count.
6816
+ */
6817
+ constructor(options) {
6818
+ this.issuer = options.issuer;
6819
+ this.step = options.step ?? 30;
6820
+ this.digits = options.digits ?? 6;
6821
+ }
6822
+ /**
6823
+ * Generate a fresh base32 secret (80 bits).
6824
+ *
6825
+ * @returns A base32-encoded TOTP secret to persist on the user row.
6826
+ */
6827
+ generateSecret() {
6828
+ return base32Encode(crypto.randomBytes(10));
6829
+ }
6830
+ /**
6831
+ * Build the `otpauth://` provisioning URI (render as a QR code).
6832
+ *
6833
+ * @param secret - The base32 secret.
6834
+ * @param accountName - Identifier shown next to the issuer (e.g. the email).
6835
+ * @returns The `otpauth://totp/...` URI.
6836
+ */
6837
+ provisioningUri(secret, accountName) {
6838
+ const label = encodeURIComponent(`${this.issuer}:${accountName}`);
6839
+ const params = new URLSearchParams({
6840
+ secret,
6841
+ issuer: this.issuer,
6842
+ algorithm: "SHA1",
6843
+ digits: String(this.digits),
6844
+ period: String(this.step)
6845
+ });
6846
+ return `otpauth://totp/${label}?${params.toString()}`;
6847
+ }
6848
+ /**
6849
+ * Verify a code against the secret for the current time window.
6850
+ *
6851
+ * @param secret - The base32 secret.
6852
+ * @param code - The submitted code.
6853
+ * @param window - Tolerance in steps (±). Default 1 (previous/current/next).
6854
+ * @returns `true` when the code matches within the window.
6855
+ */
6856
+ verify(secret, code, window = 1) {
6857
+ const cleaned = code.trim().replace(/[\s-]/g, "");
6858
+ if (!/^\d+$/.test(cleaned) || cleaned.length !== this.digits) return false;
6859
+ const key = base32Decode(secret);
6860
+ const counter = Math.floor(Date.now() / 1e3 / this.step);
6861
+ for (let offset = -window; offset <= window; offset++) {
6862
+ if (hotp(key, counter + offset, this.digits) === cleaned) return true;
6863
+ }
6864
+ return false;
6865
+ }
6866
+ };
6867
+
6868
+ // src/utils/httpClient.ts
6869
+ var CircuitOpenError = class extends Error {
6870
+ constructor(host) {
6871
+ super(`Circuit breaker is open for host ${host}`);
6872
+ this.host = host;
6873
+ this.name = "CircuitOpenError";
6874
+ }
6875
+ host;
6876
+ };
6877
+ var RetryPolicy = class {
6878
+ /**
6879
+ * @param maxRetries - Additional attempts after the first. Default 2.
6880
+ * @param baseDelayMs - Base backoff in ms (doubles each attempt). Default 100.
6881
+ * @param retryOn - HTTP status codes that trigger a retry. Default 5xx + 429.
6882
+ */
6883
+ constructor(maxRetries = 2, baseDelayMs = 100, retryOn = [429, 500, 502, 503, 504]) {
6884
+ this.maxRetries = maxRetries;
6885
+ this.baseDelayMs = baseDelayMs;
6886
+ this.retryOn = retryOn;
6887
+ }
6888
+ maxRetries;
6889
+ baseDelayMs;
6890
+ retryOn;
6891
+ /** Backoff delay in ms before `attempt` (0-indexed). */
6892
+ sleepFor(attempt) {
6893
+ return this.baseDelayMs * 2 ** attempt;
6894
+ }
6895
+ };
6896
+ var HTTPClient = class {
6897
+ baseUrl;
6898
+ defaultHeaders;
6899
+ timeoutMs;
6900
+ retryPolicy;
6901
+ breakerThreshold;
6902
+ breakerCooldownMs;
6903
+ breakers = /* @__PURE__ */ new Map();
6904
+ /**
6905
+ * @param options - Base URL, headers, timeout, retry and breaker settings.
6906
+ */
6907
+ constructor(options = {}) {
6908
+ this.baseUrl = options.baseUrl ?? "";
6909
+ this.defaultHeaders = options.defaultHeaders ?? {};
6910
+ this.timeoutMs = options.timeoutMs ?? 3e4;
6911
+ this.retryPolicy = options.retryPolicy ?? new RetryPolicy();
6912
+ this.breakerThreshold = options.breakerThreshold ?? 5;
6913
+ this.breakerCooldownMs = options.breakerCooldownMs ?? 3e4;
6914
+ }
6915
+ resolve(url) {
6916
+ return this.baseUrl && !/^https?:\/\//.test(url) ? `${this.baseUrl}${url}` : url;
6917
+ }
6918
+ hostOf(url) {
6919
+ try {
6920
+ return new URL(url).host;
6921
+ } catch {
6922
+ return url;
6923
+ }
6924
+ }
6925
+ breakerCheck(host) {
6926
+ const state = this.breakers.get(host);
6927
+ if (state && state.openUntil > Date.now()) throw new CircuitOpenError(host);
6928
+ }
6929
+ breakerRecord(host, failed) {
6930
+ const state = this.breakers.get(host) ?? { failures: 0, openUntil: 0 };
6931
+ if (failed) {
6932
+ state.failures += 1;
6933
+ if (state.failures >= this.breakerThreshold) {
6934
+ state.openUntil = Date.now() + this.breakerCooldownMs;
6935
+ state.failures = 0;
6936
+ }
6937
+ } else {
6938
+ state.failures = 0;
6939
+ state.openUntil = 0;
6940
+ }
6941
+ this.breakers.set(host, state);
6942
+ }
6943
+ /**
6944
+ * Perform a request with retries and breaker protection.
6945
+ *
6946
+ * @param method - HTTP method.
6947
+ * @param url - Absolute URL or a path resolved against `baseUrl`.
6948
+ * @param init - Extra `fetch` init (headers, body, …).
6949
+ * @returns The `Response`.
6950
+ * @throws {CircuitOpenError} When the per-host breaker is open.
6951
+ */
6952
+ async request(method, url, init = {}) {
6953
+ const target = this.resolve(url);
6954
+ const host = this.hostOf(target);
6955
+ this.breakerCheck(host);
6956
+ let lastError;
6957
+ for (let attempt = 0; attempt <= this.retryPolicy.maxRetries; attempt++) {
6958
+ const controller = new AbortController();
6959
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
6960
+ try {
6961
+ const response = await fetch(target, {
6962
+ ...init,
6963
+ method,
6964
+ headers: { ...this.defaultHeaders, ...init.headers ?? {} },
6965
+ signal: controller.signal
6966
+ });
6967
+ clearTimeout(timer);
6968
+ if (this.retryPolicy.retryOn.includes(response.status)) {
6969
+ this.breakerRecord(host, true);
6970
+ if (attempt < this.retryPolicy.maxRetries) {
6971
+ await this.sleep(this.retryPolicy.sleepFor(attempt));
6972
+ continue;
6973
+ }
6974
+ return response;
6975
+ }
6976
+ this.breakerRecord(host, false);
6977
+ return response;
6978
+ } catch (error) {
6979
+ clearTimeout(timer);
6980
+ lastError = error;
6981
+ this.breakerRecord(host, true);
6982
+ if (attempt < this.retryPolicy.maxRetries) {
6983
+ await this.sleep(this.retryPolicy.sleepFor(attempt));
6984
+ continue;
6985
+ }
6986
+ }
6987
+ }
6988
+ throw lastError instanceof Error ? lastError : new Error("Request failed");
6989
+ }
6990
+ sleep(ms) {
6991
+ return new Promise((resolve) => setTimeout(resolve, ms));
6992
+ }
6993
+ /** GET request. */
6994
+ get(url, init) {
6995
+ return this.request("GET", url, init);
6996
+ }
6997
+ /** POST request. */
6998
+ post(url, init) {
6999
+ return this.request("POST", url, init);
7000
+ }
7001
+ /** PUT request. */
7002
+ put(url, init) {
7003
+ return this.request("PUT", url, init);
7004
+ }
7005
+ /** PATCH request. */
7006
+ patch(url, init) {
7007
+ return this.request("PATCH", url, init);
7008
+ }
7009
+ /** DELETE request. */
7010
+ delete(url, init) {
7011
+ return this.request("DELETE", url, init);
7012
+ }
7013
+ };
7014
+ function readCpu() {
7015
+ const cores = os.cpus().length;
7016
+ const load1 = os.loadavg()[0] ?? 0;
7017
+ return {
7018
+ cores,
7019
+ load1,
7020
+ loadPercent: cores > 0 ? load1 / cores * 100 : 0
7021
+ };
7022
+ }
7023
+ function readMemory() {
7024
+ const total = os.totalmem();
7025
+ const free = os.freemem();
7026
+ const used = total - free;
7027
+ return {
7028
+ total,
7029
+ free,
7030
+ used,
7031
+ usedPercent: total > 0 ? used / total * 100 : 0,
7032
+ processRss: process.memoryUsage().rss
7033
+ };
7034
+ }
7035
+ function readSystem() {
7036
+ return {
7037
+ cpu: readCpu(),
7038
+ memory: readMemory(),
7039
+ uptimeSeconds: process.uptime()
7040
+ };
7041
+ }
7042
+ function toPrometheus(snapshot = readSystem()) {
7043
+ const lines = [
7044
+ "# HELP process_cpu_load_percent 1-minute load average as percent of cores",
7045
+ "# TYPE process_cpu_load_percent gauge",
7046
+ `process_cpu_load_percent ${snapshot.cpu.loadPercent}`,
7047
+ "# HELP process_memory_used_percent System memory used percent",
7048
+ "# TYPE process_memory_used_percent gauge",
7049
+ `process_memory_used_percent ${snapshot.memory.usedPercent}`,
7050
+ "# HELP process_memory_rss_bytes Resident set size of the process",
7051
+ "# TYPE process_memory_rss_bytes gauge",
7052
+ `process_memory_rss_bytes ${snapshot.memory.processRss}`,
7053
+ "# HELP process_uptime_seconds Process uptime in seconds",
7054
+ "# TYPE process_uptime_seconds counter",
7055
+ `process_uptime_seconds ${snapshot.uptimeSeconds}`
7056
+ ];
7057
+ return `${lines.join("\n")}
7058
+ `;
7059
+ }
7060
+ var MetricsUtils = {
7061
+ cpu: readCpu,
7062
+ memory: readMemory,
7063
+ system: readSystem,
7064
+ toPrometheus
7065
+ };
7066
+
7067
+ // src/utils/email.ts
7068
+ var EmailUtils = class {
7069
+ /**
7070
+ * @param options - SMTP connection and default sender.
7071
+ */
7072
+ constructor(options) {
7073
+ this.options = options;
7074
+ }
7075
+ options;
7076
+ transport = null;
7077
+ async ready() {
7078
+ if (this.transport) return this.transport;
7079
+ let nodemailer;
7080
+ try {
7081
+ const mod = await import('nodemailer');
7082
+ nodemailer = mod.default ?? mod;
7083
+ } catch (cause) {
7084
+ throw new Error(
7085
+ "EmailUtils requires the 'nodemailer' peer dependency. Install with `npm i nodemailer`.",
7086
+ { cause }
7087
+ );
7088
+ }
7089
+ this.transport = nodemailer.createTransport({
7090
+ host: this.options.host,
7091
+ port: this.options.port ?? 587,
7092
+ secure: this.options.secure ?? false,
7093
+ ...this.options.user ? { auth: { user: this.options.user, pass: this.options.password ?? "" } } : {}
7094
+ });
7095
+ return this.transport;
7096
+ }
7097
+ /**
7098
+ * Send an email message.
7099
+ *
7100
+ * @param message - The message (recipients, subject, body).
7101
+ */
7102
+ async send(message) {
7103
+ const transport = await this.ready();
7104
+ await transport.sendMail({
7105
+ from: message.from ?? this.options.from,
7106
+ to: message.to,
7107
+ subject: message.subject,
7108
+ ...message.text !== void 0 ? { text: message.text } : {},
7109
+ ...message.html !== void 0 ? { html: message.html } : {}
7110
+ });
7111
+ }
7112
+ };
7113
+
6758
7114
  // src/cache/manager.ts
6759
7115
  var MemoryCacheManager = class {
6760
7116
  store = /* @__PURE__ */ new Map();
@@ -7595,6 +7951,252 @@ function buildContentDisposition(filename, inline = false) {
7595
7951
  return `${disposition}; filename*=UTF-8''${encoded}`;
7596
7952
  }
7597
7953
 
7954
+ // src/webpush/schemas.ts
7955
+ var webPushKeysSchema = zod.z.object({
7956
+ p256dh: zod.z.string().openapi({ description: "Client public key (base64url)." }),
7957
+ auth: zod.z.string().openapi({ description: "Client auth secret (base64url)." })
7958
+ }).openapi("WebPushKeys");
7959
+ var webPushSubscriptionSchema = zod.z.object({
7960
+ endpoint: zod.z.string().url().openapi({ description: "Push service endpoint URL." }),
7961
+ keys: webPushKeysSchema
7962
+ }).openapi("WebPushSubscription");
7963
+ var webPushPayloadSchema = zod.z.object({
7964
+ title: zod.z.string().openapi({ description: "Notification title." }),
7965
+ body: zod.z.string().optional().openapi({ description: "Notification body." }),
7966
+ url: zod.z.string().optional().openapi({ description: "URL opened on click." }),
7967
+ data: zod.z.record(zod.z.unknown()).optional().openapi({ description: "Extra data." })
7968
+ }).openapi("WebPushPayload");
7969
+
7970
+ // src/webpush/dispatcher.ts
7971
+ var WebPushError = class extends Error {
7972
+ constructor(message, statusCode) {
7973
+ super(message);
7974
+ this.statusCode = statusCode;
7975
+ this.name = "WebPushError";
7976
+ }
7977
+ statusCode;
7978
+ };
7979
+ var WebPushGoneError = class extends WebPushError {
7980
+ constructor(statusCode) {
7981
+ super("Push subscription is gone", statusCode);
7982
+ this.name = "WebPushGoneError";
7983
+ }
7984
+ };
7985
+ var cached4 = null;
7986
+ async function loadWebPush() {
7987
+ if (cached4) return cached4;
7988
+ try {
7989
+ const mod = await import('web-push');
7990
+ cached4 = mod.default ?? mod;
7991
+ } catch (cause) {
7992
+ throw new Error(
7993
+ "WebPushDispatcher requires the 'web-push' peer dependency. Install with `npm i web-push`.",
7994
+ { cause }
7995
+ );
7996
+ }
7997
+ return cached4;
7998
+ }
7999
+ var WebPushDispatcher = class {
8000
+ /**
8001
+ * @param options - VAPID keys and subject.
8002
+ */
8003
+ constructor(options) {
8004
+ this.options = options;
8005
+ }
8006
+ options;
8007
+ /**
8008
+ * Send a payload to a single subscription.
8009
+ *
8010
+ * @param subscription - The browser push subscription.
8011
+ * @param payload - The notification payload.
8012
+ * @throws {WebPushGoneError} When the subscription is expired (410/404).
8013
+ * @throws {WebPushError} On any other delivery failure.
8014
+ */
8015
+ async send(subscription, payload) {
8016
+ const webpush = await loadWebPush();
8017
+ webpush.setVapidDetails(
8018
+ this.options.subject,
8019
+ this.options.publicKey,
8020
+ this.options.privateKey
8021
+ );
8022
+ try {
8023
+ await webpush.sendNotification(subscription, JSON.stringify(payload));
8024
+ } catch (error) {
8025
+ const status = error.statusCode;
8026
+ if (status === 404 || status === 410) throw new WebPushGoneError(status);
8027
+ throw new WebPushError(error.message, status);
8028
+ }
8029
+ }
8030
+ };
8031
+
8032
+ // src/integrations/provider.ts
8033
+ var inboundMessageSchema = zod.z.object({
8034
+ /** Conversation JID / sender (e.g. `5511999999999@s.whatsapp.net`). */
8035
+ from: zod.z.string().openapi({ description: "Conversation JID / sender." }),
8036
+ /** Provider message id. */
8037
+ messageId: zod.z.string().openapi({ description: "Provider message id." }),
8038
+ /** Text body, when present. */
8039
+ text: zod.z.string().optional().openapi({ description: "Text body." }),
8040
+ /** Media kind, or `null` for plain text. */
8041
+ mediaType: zod.z.enum(["image", "video", "audio", "document", "sticker"]).nullable().openapi({ description: "Media kind, or null for text." }),
8042
+ /** ISO-8601 timestamp. */
8043
+ timestamp: zod.z.string().openapi({ description: "ISO-8601 timestamp." }),
8044
+ /** Delivery direction. */
8045
+ direction: zod.z.enum(["incoming", "outgoing"]).optional()
8046
+ }).openapi("InboundMessage");
8047
+
8048
+ // src/integrations/whatsapp.ts
8049
+ var MEDIA_ROUTE = {
8050
+ image: "send-image",
8051
+ video: "send-video",
8052
+ audio: "send-audio",
8053
+ document: "send-document"
8054
+ };
8055
+ function deriveWsUrl(baseUrl) {
8056
+ const trimmed = baseUrl.replace(/\/$/, "");
8057
+ return `${trimmed.replace(/^http/, "ws")}/ws`;
8058
+ }
8059
+ var WhatsAppProvider = class {
8060
+ http;
8061
+ apiKey;
8062
+ wsUrl;
8063
+ /**
8064
+ * @param options - Base URL, API key and optional WebSocket URL.
8065
+ */
8066
+ constructor(options) {
8067
+ this.apiKey = options.apiKey;
8068
+ this.wsUrl = options.wsUrl ?? deriveWsUrl(options.baseUrl);
8069
+ this.http = new HTTPClient({
8070
+ baseUrl: options.baseUrl.replace(/\/$/, ""),
8071
+ defaultHeaders: { "x-api-key": options.apiKey, "content-type": "application/json" },
8072
+ timeoutMs: options.timeoutMs ?? 15e3
8073
+ });
8074
+ }
8075
+ /** POST JSON and parse the response, throwing on a non-2xx status. */
8076
+ async postJson(path, body, options) {
8077
+ const headers = options?.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : void 0;
8078
+ const res = await this.http.post(path, {
8079
+ body: JSON.stringify(body),
8080
+ ...headers ? { headers } : {}
8081
+ });
8082
+ const data = await res.json().catch(() => ({}));
8083
+ if (!res.ok) {
8084
+ throw new Error(
8085
+ `zap-api ${path} failed (${res.status}): ${String(data.error ?? res.statusText)}`
8086
+ );
8087
+ }
8088
+ return data;
8089
+ }
8090
+ async sendText(to, text, options) {
8091
+ const data = await this.postJson("/message/send-text", { to, text }, options);
8092
+ return {
8093
+ status: String(data.status ?? "queued"),
8094
+ ...typeof data.id === "string" ? { id: data.id } : {},
8095
+ ...typeof data.deduped === "boolean" ? { deduped: data.deduped } : {}
8096
+ };
8097
+ }
8098
+ async sendMedia(to, media, options) {
8099
+ const body = { to, media: media.media };
8100
+ if (media.caption !== void 0) body.caption = media.caption;
8101
+ if (media.fileName !== void 0) body.fileName = media.fileName;
8102
+ const data = await this.postJson(
8103
+ `/message/${MEDIA_ROUTE[media.kind]}`,
8104
+ body,
8105
+ options
8106
+ );
8107
+ return {
8108
+ status: String(data.status ?? "queued"),
8109
+ ...typeof data.id === "string" ? { id: data.id } : {},
8110
+ ...typeof data.deduped === "boolean" ? { deduped: data.deduped } : {}
8111
+ };
8112
+ }
8113
+ async checkNumber(number) {
8114
+ const res = await this.http.get(
8115
+ `/message/check-number/${encodeURIComponent(number)}`
8116
+ );
8117
+ const data = await res.json().catch(() => ({}));
8118
+ return data.exists === true;
8119
+ }
8120
+ async status() {
8121
+ const res = await this.http.get("/session/status");
8122
+ const raw = await res.text();
8123
+ try {
8124
+ const parsed = JSON.parse(raw);
8125
+ return parsed.status ?? raw.trim();
8126
+ } catch {
8127
+ return raw.trim();
8128
+ }
8129
+ }
8130
+ /** Start the WhatsApp session (returns the authenticated QR URL, if any). */
8131
+ async startSession() {
8132
+ return this.postJson("/session/start", {});
8133
+ }
8134
+ async onMessage(handler, room = "*") {
8135
+ let ws;
8136
+ try {
8137
+ ws = await import('ws');
8138
+ } catch (cause) {
8139
+ throw new Error(
8140
+ "WhatsAppProvider.onMessage requires the 'ws' peer dependency. Install with `npm i ws`.",
8141
+ { cause }
8142
+ );
8143
+ }
8144
+ const socket = new ws.WebSocket(this.wsUrl, {
8145
+ headers: { "x-api-key": this.apiKey }
8146
+ });
8147
+ socket.on("open", () => {
8148
+ socket.send(JSON.stringify({ action: "subscribe", room }));
8149
+ });
8150
+ socket.on("message", (raw) => {
8151
+ let frame;
8152
+ try {
8153
+ frame = JSON.parse(String(raw));
8154
+ } catch {
8155
+ return;
8156
+ }
8157
+ if (frame.type === "message" && frame.payload) {
8158
+ const p = frame.payload;
8159
+ void handler({
8160
+ from: String(p.remoteJid ?? ""),
8161
+ messageId: String(p.messageId ?? ""),
8162
+ ...typeof p.text === "string" ? { text: p.text } : {},
8163
+ mediaType: p.mediaType ?? null,
8164
+ timestamp: String(p.timestamp ?? ""),
8165
+ ...p.direction === "incoming" || p.direction === "outgoing" ? { direction: p.direction } : {}
8166
+ });
8167
+ }
8168
+ });
8169
+ return async () => {
8170
+ try {
8171
+ socket.send(JSON.stringify({ action: "unsubscribe", room }));
8172
+ } catch {
8173
+ }
8174
+ socket.close();
8175
+ };
8176
+ }
8177
+ };
8178
+ function safeEqual(a, b) {
8179
+ const bufA = Buffer.from(a);
8180
+ const bufB = Buffer.from(b);
8181
+ return bufA.length === bufB.length && crypto.timingSafeEqual(bufA, bufB);
8182
+ }
8183
+ function makeWhatsAppWebhookRouter(options) {
8184
+ const path = options.path ?? "/whatsapp/inbound";
8185
+ const router = express2.Router();
8186
+ router.post(path, async (req, res) => {
8187
+ if (options.apiKey) {
8188
+ const provided = req.header("x-api-key") ?? "";
8189
+ if (!safeEqual(provided, options.apiKey)) {
8190
+ throw new UnauthorizedException({ message: "Invalid webhook key" });
8191
+ }
8192
+ }
8193
+ const message = inboundMessageSchema.parse(req.body);
8194
+ await options.onMessage(message);
8195
+ res.status(200).json({ ok: true });
8196
+ });
8197
+ return router;
8198
+ }
8199
+
7598
8200
  // src/auth/schemas.ts
7599
8201
  var signupSchema = zod.z.object({
7600
8202
  email: zod.z.string().email().openapi({ description: "Login identifier (email)." }),
@@ -8107,7 +8709,7 @@ function runServer(app, options = {}) {
8107
8709
  }
8108
8710
 
8109
8711
  // src/version.ts
8110
- var VERSION = "0.2.0";
8712
+ var VERSION = "0.4.0";
8111
8713
 
8112
8714
  Object.defineProperty(exports, "OpenAPIRegistry", {
8113
8715
  enumerable: true,
@@ -8269,14 +8871,17 @@ exports.BaseService = BaseService;
8269
8871
  exports.CEP_PATTERN = CEP_PATTERN;
8270
8872
  exports.CNPJ_PATTERN = CNPJ_PATTERN;
8271
8873
  exports.CPF_PATTERN = CPF_PATTERN;
8874
+ exports.CircuitOpenError = CircuitOpenError;
8272
8875
  exports.CompositeFeatureFlagBackend = CompositeFeatureFlagBackend;
8273
8876
  exports.ConflictException = ConflictException;
8274
8877
  exports.DEFAULT_LOCALE = DEFAULT_LOCALE;
8878
+ exports.EmailUtils = EmailUtils;
8275
8879
  exports.EnvFeatureFlagBackend = EnvFeatureFlagBackend;
8276
8880
  exports.EventStream = EventStream;
8277
8881
  exports.ExpiredTokenException = ExpiredTokenException;
8278
8882
  exports.FeatureFlags = FeatureFlags;
8279
8883
  exports.ForbiddenException = ForbiddenException;
8884
+ exports.HTTPClient = HTTPClient;
8280
8885
  exports.HTTP_500_MARKER = HTTP_500_MARKER;
8281
8886
  exports.InvalidTokenException = InvalidTokenException;
8282
8887
  exports.JSONLogger = JSONLogger;
@@ -8288,6 +8893,7 @@ exports.MemoryFeatureFlagBackend = MemoryFeatureFlagBackend;
8288
8893
  exports.MemorySessionStore = MemorySessionStore;
8289
8894
  exports.MemoryThrottleBackend = MemoryThrottleBackend;
8290
8895
  exports.MessageCatalog = MessageCatalog;
8896
+ exports.MetricsUtils = MetricsUtils;
8291
8897
  exports.NotFoundException = NotFoundException;
8292
8898
  exports.PHONE_BR_PATTERN = PHONE_BR_PATTERN;
8293
8899
  exports.PasswordUtils = PasswordUtils;
@@ -8295,9 +8901,11 @@ exports.REQUEST_ID_HEADER = REQUEST_ID_HEADER;
8295
8901
  exports.RabbitBroker = RabbitBroker;
8296
8902
  exports.RedisCacheManager = RedisCacheManager;
8297
8903
  exports.Region = Region;
8904
+ exports.RetryPolicy = RetryPolicy;
8298
8905
  exports.SSEBroker = SSEBroker;
8299
8906
  exports.ServerSentEvent = ServerSentEvent;
8300
8907
  exports.SessionService = SessionService;
8908
+ exports.TOTPHelper = TOTPHelper;
8301
8909
  exports.TaskManager = TaskManager;
8302
8910
  exports.TooManyRequestsException = TooManyRequestsException;
8303
8911
  exports.UF = UF;
@@ -8305,7 +8913,11 @@ exports.UnauthorizedException = UnauthorizedException;
8305
8913
  exports.UserAuthService = UserAuthService;
8306
8914
  exports.VERSION = VERSION;
8307
8915
  exports.ValidationException = ValidationException;
8916
+ exports.WebPushDispatcher = WebPushDispatcher;
8917
+ exports.WebPushError = WebPushError;
8918
+ exports.WebPushGoneError = WebPushGoneError;
8308
8919
  exports.WebSocketHub = WebSocketHub;
8920
+ exports.WhatsAppProvider = WhatsAppProvider;
8309
8921
  exports.attachWebSocketHub = attachWebSocketHub;
8310
8922
  exports.authResponseSchema = authResponseSchema;
8311
8923
  exports.baseAppSettingsSchema = baseAppSettingsSchema;
@@ -8336,11 +8948,13 @@ exports.encodeCursor = encodeCursor;
8336
8948
  exports.generateOpaqueToken = generateOpaqueToken;
8337
8949
  exports.generateOpenApiDocument = generateOpenApiDocument;
8338
8950
  exports.getAuth = getAuth;
8951
+ exports.getClientIp = getClientIp;
8339
8952
  exports.getConditions = getConditions;
8340
8953
  exports.getPaginationConditions = getPaginationConditions;
8341
8954
  exports.getRequestId = getRequestId;
8342
8955
  exports.getState = getState;
8343
8956
  exports.hashOpaqueToken = hashOpaqueToken;
8957
+ exports.inboundMessageSchema = inboundMessageSchema;
8344
8958
  exports.isValidCep = isValidCep;
8345
8959
  exports.isValidCity = isValidCity;
8346
8960
  exports.isValidCnpj = isValidCnpj;
@@ -8358,6 +8972,7 @@ exports.makeHealthRouter = makeHealthRouter;
8358
8972
  exports.makeJwtAuthMiddleware = makeJwtAuthMiddleware;
8359
8973
  exports.makeSessionMiddleware = makeSessionMiddleware;
8360
8974
  exports.makeUnhandledExceptionHandler = makeUnhandledExceptionHandler;
8975
+ exports.makeWhatsAppWebhookRouter = makeWhatsAppWebhookRouter;
8361
8976
  exports.modifyDict = modifyDict;
8362
8977
  exports.mountOpenApiJson = mountOpenApiJson;
8363
8978
  exports.mountRedoc = mountRedoc;
@@ -8397,6 +9012,9 @@ exports.updatedByColumn = updatedByColumn;
8397
9012
  exports.userPublicSchema = userPublicSchema;
8398
9013
  exports.utcnow = utcnow;
8399
9014
  exports.verifyOpaqueToken = verifyOpaqueToken;
9015
+ exports.webPushKeysSchema = webPushKeysSchema;
9016
+ exports.webPushPayloadSchema = webPushPayloadSchema;
9017
+ exports.webPushSubscriptionSchema = webPushSubscriptionSchema;
8400
9018
  exports.wsEnvelopeSchema = wsEnvelopeSchema;
8401
9019
  //# sourceMappingURL=index.cjs.map
8402
9020
  //# sourceMappingURL=index.cjs.map