/** * Doorman entrypoint */ const { randomUUID } = require('crypto'); const fetch = require('node-fetch'); async function getConfig(context, buzzer) { return await fetch(context.DOORMAN_URL + `/api/door/info?buzzer=${buzzer}`) .then(res => res.json()) .catch(err => { return undefined; }); } function notifyDiscord(context, msg, u, optionalJsonStr) { return fetch(context.DOORMAN_URL + `/api/door/notify?discordUser=${encodeURIComponent(JSON.stringify(u))}&msg=${encodeURIComponent(JSON.stringify(msg))}&json=${encodeURIComponent(JSON.stringify(optionalJsonStr))}`, ).catch(err => console.log(err)) } function notifyAllDiscord(context, config, msg, optionalJsonStr) { return notifyDiscord(context, config.discordUsers.map(() => msg), config.discordUsers, config.discordUsers.map(() => optionalJsonStr).filter(Boolean)); } function doorOpenTwiml(twiml, config) { twiml.play('https://buzzer-2439-prod.twil.io/buzzing_up_boosted.mp3'); twiml.play({ digits: config.pressKey }); // configured in doorman what button to click and passed into this function twiml.pause({ length: 1 }); twiml.hangup(); } function dialFallbackTwiml(twiml, config) { let dial = twiml.dial({ timeLimit: 20, timeout: 20 }); config.fallbackNumbers.forEach(number => { dial.number(number); }); } exports.handler = async function(context, event, callback) { let invokeId = `[${randomUUID()}]`; let config = event.config; console.log(invokeId, "starting execution"); // get by api or parse it out from query if (!config) { config = await getConfig(context, event.From); } else { try { config = JSON.parse(config); } catch(e) { config = await getConfig(context, event.From); } } // reject the call if this is not configured if (!config || !config.door) { let twiml = new Twilio.twiml.VoiceResponse(); twiml.reject(); callback(null, twiml); return; } // let users know someone is currently buzzing, and allow unlock by discord user let msg = `🔔 Someone is dialing right now @ Door "${config.door}" [Click to unlock](${context.DOORMAN_URL}/api/door/auth?door=${config.door}&key=`; let msgs = config.discordUsers.map((u) => msg + u + ')' ); notifyDiscord(context, msgs, config.discordUsers, []); let discordLock = false; let intervals = []; let timeouts = []; const unlockPromise = new Promise((resolve, reject) => { intervals.push(setInterval(() => { fetch(context.DOORMAN_URL + `/api/door/status?door=${config.door}`) .then(async res => { if (res.status === 200) { clearInterval(intervals[0]); const body = await res.json(); const twiml = new Twilio.twiml.VoiceResponse(); doorOpenTwiml(twiml, config); if (!discordLock) { discordLock = true; console.log( invokeId, "UnlockPromise: I was the fastest, so I will attempt to notify discord users before resolving with unlock" ); await notifyAllDiscord(context, config, `🔓 Doorman buzzed someone up @ Door "${config.door}"`, JSON.stringify(body.fingerprint)); resolve(twiml); } else { console.log( invokeId, "UnlockPromise: dropping out of the race, graceful fallback is already notifiying discord users" ); } } }).catch(err => console.log(invokeId, err)); }, 750)); }); const gracefulFallbackPromise = new Promise((resolve, reject) => { timeouts.push(setTimeout(async () => { const twiml = new Twilio.twiml.VoiceResponse(); dialFallbackTwiml(twiml, config); if (!discordLock) { discordLock = true; console.log( invokeId, "GracefulFallbackPromise: I was the fastest, so I will attempt to notify discord users before resolving with a call" ); await notifyAllDiscord( context, config, `📞 Somebody buzzed the door and it dialed through to fallback phone numbers @ Door "${config.door}"`, undefined ); resolve(twiml); } else { console.log( invokeId, "GracefulFallbackPromise: dropping out of the race, unlock is already notifying discord users" ); } }, 6000)); }); const ungracefulFallbackPromise = new Promise((resolve, reject) => { timeouts.push(setTimeout(async () => { const twiml = new Twilio.twiml.VoiceResponse(); dialFallbackTwiml(twiml, config); console.error( invokeId, "UngracefulFallbackPromise: Cutting it too close to timeout! Skipping notifying users and calling fallback" ); resolve(twiml); }, 9500)); }); const twiml = await Promise.race([unlockPromise, gracefulFallbackPromise, ungracefulFallbackPromise]); console.log(invokeId, "Race ended, clearing residual timers"); timeouts.forEach(timeout => clearTimeout(timeout)); intervals.forEach(interval => clearInterval(interval)); callback(null, twiml); };