buzz-me-in 2.2.0-beta.1 → 2.2.0-beta.2
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/.env.example +7 -0
- package/README.md +5 -0
- package/homekit.js +95 -4
- package/package.json +1 -1
- package/src/core/config.js +1 -0
package/.env.example
CHANGED
|
@@ -55,6 +55,13 @@ TWILIO_MEDIA_PAYLOAD_MAX_BYTES=512
|
|
|
55
55
|
TWILIO_UNLOCK_DIGITS=w9w
|
|
56
56
|
# Grace period for SIGINT/SIGTERM graceful shutdown before forced exit.
|
|
57
57
|
SHUTDOWN_GRACE_MS=10000
|
|
58
|
+
# How long the Twilio call is held open after the HomeKit live view closes,
|
|
59
|
+
# before the call is hung up. Backing out of the camera view to the room view
|
|
60
|
+
# tears the stream down, and the lock tile there needs the call to still be up
|
|
61
|
+
# to send its DTMF. Raise this if you find yourself reaching the lock too late;
|
|
62
|
+
# the only cost is that much silence for the caller after you are done.
|
|
63
|
+
# Tapping unlock restarts the window, so the digits are never cut short.
|
|
64
|
+
HOMEKIT_HANGUP_GRACE_MS=3000
|
|
58
65
|
|
|
59
66
|
# Logging
|
|
60
67
|
# LOG_LEVEL controls minimum emitted level: debug | info | warn | error
|
package/README.md
CHANGED
|
@@ -422,6 +422,8 @@ Tap the lock tile in the Home app → set to Unlocked.
|
|
|
422
422
|
|
|
423
423
|
The lock tile resets to Secured automatically after 3 seconds.
|
|
424
424
|
|
|
425
|
+
Two routes reach the lock tile, and they behave differently. The grid button at the bottom right of the camera view brings the lock up _beside_ the running stream, which is the smoother of the two. Backing out to the room view instead closes the camera view and tears the stream down; the lock tile is still there under **Other**, and it works because the call is held open for `HOMEKIT_HANGUP_GRACE_MS` after the view closes. Tapping unlock restarts that window, so the digits are never cut off mid-sequence.
|
|
426
|
+
|
|
425
427
|
---
|
|
426
428
|
|
|
427
429
|
### Stage 8 — Caller hangs up
|
|
@@ -446,10 +448,13 @@ Open the live view, then dismiss it on the iPhone (tap the X / end button).
|
|
|
446
448
|
**Pass (server log):**
|
|
447
449
|
|
|
448
450
|
```text
|
|
451
|
+
{"ts":"...","level":"info","message":"Hangup scheduled after grace period","component":"homekit","event":"hangup-scheduled","reason":"homekit-session-stopped","callSid":"CA...","sessionId":"...","graceMs":3000}
|
|
449
452
|
{"ts":"...","level":"info","message":"Inbound ffmpeg exited","component":"homekit","event":"ffin-exit","reason":"nonzero-exit","exitCode":null,"sessionId":"..."}
|
|
450
453
|
{"ts":"...","level":"info","message":"Hanging up call","component":"twilio-api","event":"hangup","callSid":"CA..."}
|
|
451
454
|
```
|
|
452
455
|
|
|
456
|
+
The call is not hung up the instant the view closes. It is held open for `HOMEKIT_HANGUP_GRACE_MS` (default 3000) so the lock tile still has a call to send DTMF to, then hung up. Reopening the view inside that window logs `hangup-cancelled` instead and the call continues; if the caller hangs up first, the pending hangup logs `hangup-skipped` and does nothing.
|
|
457
|
+
|
|
453
458
|
**Pass (Twilio):** The call shows as completed in the [Twilio Console call log](https://console.twilio.com/us1/monitor/logs/calls).
|
|
454
459
|
|
|
455
460
|
---
|
package/homekit.js
CHANGED
|
@@ -277,6 +277,9 @@ const streamingDelegate = {
|
|
|
277
277
|
// ---------------------------------------------------------------------------
|
|
278
278
|
|
|
279
279
|
function _startSession(sessionID, s, request, callback) {
|
|
280
|
+
// Reopening the live view inside the grace window means the user is not
|
|
281
|
+
// finished after all.
|
|
282
|
+
cancelPendingHangUp('homekit-session-restarted');
|
|
280
283
|
const videoParams = srtpParams(s.hkVideoKey, s.hkVideoSalt);
|
|
281
284
|
const audioParams = srtpParams(s.hkAudioKey, s.hkAudioSalt);
|
|
282
285
|
const video = request.video;
|
|
@@ -557,13 +560,85 @@ function _stopSession(sessionID, hangUp) {
|
|
|
557
560
|
|
|
558
561
|
const activeCall = getActiveCall();
|
|
559
562
|
if (hangUp && activeCall) {
|
|
560
|
-
|
|
563
|
+
scheduleHangUp(activeCall.callSid, sessionID);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
// Deferred hangup
|
|
569
|
+
//
|
|
570
|
+
// Closing the live view is not the same as being finished with the caller. The
|
|
571
|
+
// Home app reaches the lock tile two different ways: the grid button inside the
|
|
572
|
+
// camera view keeps the stream up, but backing out to the room view tears it
|
|
573
|
+
// down, and tapping the lock from there used to find no call left to send DTMF
|
|
574
|
+
// to. Holding the call open briefly after STOP makes both routes work, because
|
|
575
|
+
// unlock needs the call's WebSocket, not a HomeKit streaming session.
|
|
576
|
+
//
|
|
577
|
+
// The call is still hung up when the user really has finished; it just costs
|
|
578
|
+
// config.homekitHangupGraceMs of silence first, ffmpeg having already been
|
|
579
|
+
// killed above.
|
|
580
|
+
// ---------------------------------------------------------------------------
|
|
581
|
+
|
|
582
|
+
const HANGUP_GRACE_MS = config.homekitHangupGraceMs;
|
|
583
|
+
|
|
584
|
+
/** @type {{ timer: NodeJS.Timeout, callSid: string, sessionID: string } | null} */
|
|
585
|
+
let pendingHangUp = null;
|
|
586
|
+
|
|
587
|
+
function cancelPendingHangUp(reason) {
|
|
588
|
+
if (!pendingHangUp) return;
|
|
589
|
+
clearTimeout(pendingHangUp.timer);
|
|
590
|
+
logger.info('Pending hangup cancelled', {
|
|
591
|
+
event: 'hangup-cancelled',
|
|
592
|
+
reason,
|
|
593
|
+
callSid: pendingHangUp.callSid,
|
|
594
|
+
sessionId: pendingHangUp.sessionID,
|
|
595
|
+
});
|
|
596
|
+
pendingHangUp = null;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Restart the grace window, so a hangup cannot land on top of an action the
|
|
601
|
+
* user has just taken. Without this an unlock tapped late in the window could
|
|
602
|
+
* be cut off before the intercom acts on the digits.
|
|
603
|
+
*/
|
|
604
|
+
function deferPendingHangUp(reason) {
|
|
605
|
+
if (!pendingHangUp) return;
|
|
606
|
+
const { callSid, sessionID } = pendingHangUp;
|
|
607
|
+
clearTimeout(pendingHangUp.timer);
|
|
608
|
+
pendingHangUp = null;
|
|
609
|
+
scheduleHangUp(callSid, sessionID, reason);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function scheduleHangUp(callSid, sessionID, reason = 'homekit-session-stopped') {
|
|
613
|
+
cancelPendingHangUp('superseded');
|
|
614
|
+
logger.info('Hangup scheduled after grace period', {
|
|
615
|
+
event: 'hangup-scheduled',
|
|
616
|
+
reason,
|
|
617
|
+
callSid,
|
|
618
|
+
sessionId: sessionID,
|
|
619
|
+
graceMs: HANGUP_GRACE_MS,
|
|
620
|
+
});
|
|
621
|
+
const timer = setTimeout(() => {
|
|
622
|
+
pendingHangUp = null;
|
|
623
|
+
const activeCall = getActiveCall();
|
|
624
|
+
// The caller may have hung up during the window, and the slot may already
|
|
625
|
+
// hold a different call. Either way this one is no longer ours to end.
|
|
626
|
+
if (!activeCall || activeCall.callSid !== callSid) {
|
|
627
|
+
logger.info('Pending hangup skipped; call already gone', {
|
|
628
|
+
event: 'hangup-skipped',
|
|
629
|
+
reason: 'call-no-longer-active',
|
|
630
|
+
callSid,
|
|
631
|
+
sessionId: sessionID,
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
hangUpCall(callSid)
|
|
561
636
|
.then((result) => {
|
|
562
637
|
if (result && result.alreadyEnded) {
|
|
563
638
|
logger.info('Twilio call already ended while stopping HomeKit session', {
|
|
564
639
|
event: 'hangup-call-already-ended',
|
|
565
640
|
reason: 'twilio-call-not-in-progress',
|
|
566
|
-
callSid
|
|
641
|
+
callSid,
|
|
567
642
|
sessionId: sessionID,
|
|
568
643
|
});
|
|
569
644
|
}
|
|
@@ -572,12 +647,14 @@ function _stopSession(sessionID, hangUp) {
|
|
|
572
647
|
logger.error('Failed to hang up call while stopping HomeKit session', {
|
|
573
648
|
event: 'hangup-call-failed',
|
|
574
649
|
reason: 'twilio-hangup-failed',
|
|
575
|
-
callSid
|
|
650
|
+
callSid,
|
|
576
651
|
sessionId: sessionID,
|
|
577
652
|
error,
|
|
578
653
|
});
|
|
579
654
|
});
|
|
580
|
-
}
|
|
655
|
+
}, HANGUP_GRACE_MS);
|
|
656
|
+
timer.unref();
|
|
657
|
+
pendingHangUp = { timer, callSid, sessionID };
|
|
581
658
|
}
|
|
582
659
|
|
|
583
660
|
// ---------------------------------------------------------------------------
|
|
@@ -611,6 +688,10 @@ lockService
|
|
|
611
688
|
try {
|
|
612
689
|
await sendDtmfSequence(activeCall, config.twilioUnlockDigits);
|
|
613
690
|
state.markActivity(activeCall.callSid, 'unlock-dtmf');
|
|
691
|
+
// Unlocking from the room view is the case this grace window exists
|
|
692
|
+
// for, so give the intercom a full window to act on the digits rather
|
|
693
|
+
// than whatever was left of the previous one.
|
|
694
|
+
deferPendingHangUp('unlock-sent');
|
|
614
695
|
logger.info('Sent DTMF unlock over active media stream', {
|
|
615
696
|
event: 'unlock-requested',
|
|
616
697
|
callSid: activeCall.callSid,
|
|
@@ -789,6 +870,9 @@ function clearMulawPassthrough(stream) {
|
|
|
789
870
|
* Does NOT call hangUpCall — the call is already gone.
|
|
790
871
|
*/
|
|
791
872
|
function endHapSession() {
|
|
873
|
+
// The caller is already gone, so a hangup left over from a closed live view
|
|
874
|
+
// has nothing to end.
|
|
875
|
+
cancelPendingHangUp('call-ended');
|
|
792
876
|
for (const sessionID of activeSessions.keys()) {
|
|
793
877
|
_stopSession(sessionID, /* hangUp= */ false);
|
|
794
878
|
}
|
|
@@ -811,6 +895,7 @@ function setOnHapSessionStarted(handler) {
|
|
|
811
895
|
* clean stop, restart and reboot.
|
|
812
896
|
*/
|
|
813
897
|
function shutdown() {
|
|
898
|
+
cancelPendingHangUp('shutdown');
|
|
814
899
|
endHapSession();
|
|
815
900
|
removeSdpDir();
|
|
816
901
|
// Nothing to tear down if start() never ran: unpublish() on an accessory
|
|
@@ -833,4 +918,10 @@ module.exports = {
|
|
|
833
918
|
endHapSession,
|
|
834
919
|
setOnHapSessionStarted,
|
|
835
920
|
shutdown,
|
|
921
|
+
// Exported for tests/homekit-hangup-grace.test.cjs. Reaching the deferred
|
|
922
|
+
// hangup through the streaming delegate would mean standing up a full SRTP
|
|
923
|
+
// session, which tests nothing about the timer.
|
|
924
|
+
scheduleHangUp,
|
|
925
|
+
cancelPendingHangUp,
|
|
926
|
+
deferPendingHangUp,
|
|
836
927
|
};
|
package/package.json
CHANGED
package/src/core/config.js
CHANGED
|
@@ -138,6 +138,7 @@ module.exports = {
|
|
|
138
138
|
streamStartTimeoutMs: parsePositiveInt('STREAM_START_TIMEOUT_MS', '3000'),
|
|
139
139
|
twilioMediaPayloadMaxBytes: parsePositiveInt('TWILIO_MEDIA_PAYLOAD_MAX_BYTES', '512'),
|
|
140
140
|
shutdownGraceMs: parsePositiveInt('SHUTDOWN_GRACE_MS', '10000'),
|
|
141
|
+
homekitHangupGraceMs: parsePositiveInt('HOMEKIT_HANGUP_GRACE_MS', '3000'),
|
|
141
142
|
streamAuthSecret,
|
|
142
143
|
streamAuthTtlSec: 90,
|
|
143
144
|
statusApiToken,
|