/** * Doorman entrypoint */ 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=${u}&msg=${encodeURIComponent(msg)}&json=${encodeURIComponent(optionalJsonStr)}`, ).catch(err => console.log(err)) } function notifyAllDiscord(context, config, msg, optionalJsonStr) { return Promise.all(config.discordUsers.map((u) => notifyDiscord(context, msg, u, optionalJsonStr) )); } 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 config = event.config; // 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=`; // TODO: should we await for one delivery? Promise.race(config.discordUsers.map((u) => notifyDiscord(context, msg + u + ')', u) )); 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( "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( "UnlockPromise: dropping out of the race, graceful fallback is already notifiying discord users" ); } } }).catch(err => console.log(err)); }, 500)); }); 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( "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}"` ); resolve(twiml); } else { console.log( "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( "UngracefulFallbackPromise: Cutting it too close to timeout! Skipping notifying users and calling fallback" ); resolve(twiml); }, 8000)); }); const twiml = await Promise.race([unlockPromise, gracefulFallbackPromise, ungracefulFallbackPromise]); console.log("Race ended, clearing residual timers"); timeouts.forEach(timeout => clearTimeout(timeout)); intervals.forEach(interval => clearInterval(interval)); callback(null, twiml); };