totem-otp-delivery-webhook
Version:
Webhook delivery agent for TotemOTP
145 lines • 5.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebhookDeliveryAgent = void 0;
// Use Node.js built-in fetch (available in Node 18+) or fallback to https module
const https_1 = require("https");
const http_1 = require("http");
const url_1 = require("url");
class WebhookDeliveryAgent {
constructor(options) {
this.webhookUrl = options.webhookUrl;
this.bodyBuilder = options.bodyBuilder || this.defaultBodyBuilder;
this.headers = {
'Content-Type': 'application/json',
...options.headers
};
this.method = options.method || 'POST';
this.timeout = options.timeout || 30000;
}
async sendMessageToAudience(otp) {
const body = this.bodyBuilder(otp);
const bodyString = typeof body === 'string' ? body : JSON.stringify(body);
try {
const response = await this.makeHttpRequest(bodyString);
if (response.statusCode < 200 || response.statusCode >= 300) {
throw new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`);
}
// Try to get receipt ID from response
const receiptId = await this.extractReceiptId(response);
return receiptId || this.generateReceiptId(otp);
}
catch (error) {
throw new Error(`Webhook delivery failed: ${error.message}`);
}
}
/**
* Default body builder that creates a standard webhook payload
*/
defaultBodyBuilder(otp) {
return {
event: 'otp_requested',
timestamp: new Date().toISOString(),
data: {
target: {
type: otp.target.type,
value: otp.target.value,
uniqueIdentifier: otp.target.uniqueIdentifier
},
otp: {
value: otp.value,
reference: otp.reference,
expiresAt: new Date(otp.expiresAtMs).toISOString(),
resendAllowedAt: new Date(otp.resendAllowedAtMs).toISOString()
}
}
};
}
/**
* Make HTTP request using Node.js built-in modules
*/
makeHttpRequest(body) {
return new Promise((resolve, reject) => {
const url = new url_1.URL(this.webhookUrl);
const isHttps = url.protocol === 'https:';
const request = isHttps ? https_1.request : http_1.request;
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname + url.search,
method: this.method,
headers: {
...this.headers,
'Content-Length': Buffer.byteLength(body)
},
timeout: this.timeout
};
const req = request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
resolve({
statusCode: res.statusCode || 0,
statusMessage: res.statusMessage || '',
headers: res.headers,
data
});
});
});
req.on('error', (error) => {
reject(error);
});
req.on('timeout', () => {
req.destroy();
reject(new Error(`Request timeout after ${this.timeout}ms`));
});
req.write(body);
req.end();
});
}
/**
* Extract receipt ID from webhook response
*/
async extractReceiptId(response) {
try {
const contentType = response.headers['content-type'] || '';
if (contentType.includes('application/json')) {
const data = JSON.parse(response.data);
// Try common receipt ID field names
return (data.receiptId || data.receipt_id || data.id || data.messageId || data.message_id || null);
}
// If response is text, try to use it as receipt ID
if (contentType.includes('text/') || !contentType) {
const text = response.data.trim();
return text || null;
}
return null;
}
catch {
return null;
}
}
/**
* Generate a fallback receipt ID if none is provided by the webhook
*/
generateReceiptId(otp) {
const timestamp = Date.now();
const targetHash = this.simpleHash(otp.target.value);
return `webhook_${timestamp}_${targetHash}_${otp.reference}`;
}
/**
* Simple hash function for generating receipt IDs
*/
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash).toString(16);
}
}
exports.WebhookDeliveryAgent = WebhookDeliveryAgent;
//# sourceMappingURL=WebhookDeliveryAgent.js.map