Overflow Tap: speak Overflow's Dashboard API, as its own helper app does

The first cut guessed a write route on Overflow's public API, which documents
only reads for Tap. With the helper app's bundle in hand (Overflow ProPresenter
Integration 1.2.3, read 2026-10-11) the module now makes exactly the calls the
helper makes, against the Dashboard's own backend:

  POST /nonprofit-dashboard/authentication/login/jwt      a Dashboard sign-in → JWT
  GET  /nonprofit-dashboard/propresenter/nonprofits/{id}/keywords
                                                          the Keyword Mappings
  POST …/nonprofits/{id}/keywords?syncNonprofit=true      { keywords } = the switch

So the keyword → destination → Tap groups mapping stays where Overflow keeps
it (Dashboard › Integrations › ProPresenter › Keyword Mappings) and is read
from there: on start, every five minutes, on the tile's refresh button, and
from every trigger's answer. Admin config shrinks to the ProPresenter endpoint,
the Overflow Dashboard login (plus a Nonprofit ID only for an @overflow.co
staff login) and the alert switch; the API-key, Tap-groups, keyword-text and
match-mode fields are gone.

Behaviour follows the helper: both the presentation layer (/v1/status/slide)
and the announcement layer (/v1/announcement/slide_index + /active) are
watched; when a layer's set of matched keywords changes to a non-empty set it
is sent; matching is per word, case-insensitive, punctuation around the word
ignored. The session token is cached across re-inits for the same login so an
admin saving config repeatedly cannot trip Overflow's "too many attempts";
a 401 signs in again once; a refused sign-in is not retried within 20 s.

tools/overflow-tap-mock.js now plays the Dashboard API (any email/password
signs in; --fail-switch, --expire=<s>, --login=<email>:<password>) and gives
the mock ProPresenter an announcement layer (--no-announcements for an older
one). Verified against it: sign-in, keyword load, triggers on both layers,
token expiry → re-login, 500 → NOT SWITCHED + alert + retries, wrong login →
the reason on the tile and in the health line, and recovery. keywords.selftest.js
pins the matching rule (8 cases).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-10-11 10:25:02 -04:00
co-authored by Claude Fable 5.1
parent 6b51044be6
commit 459d8b9c38
9 changed files with 738 additions and 622 deletions
+10 -42
View File
@@ -23,14 +23,11 @@
},
"configGroups": {
"ProPresenter": {
"help": "The ProPresenter whose slide notes carry the keywords. Enable the network in ProPresenter's Preferences › Network and enter the host and port shown there. Only the live slide's notes are read — nothing on the stage screens changes."
"help": "The ProPresenter whose slide notes carry the keywords. Enable the network in ProPresenter's Preferences › Network and enter the host and port shown there. Only the live slide's notes (and the announcement layer's) are read — nothing on the stage screens changes."
},
"Overflow": {
"collapsed": "whenSet",
"help": "API credentials from the Overflow Dashboard: Developers › API Keys (a READ_WRITE key). Tap groups and destinations are read from the account once these are saved; the groups list below fills in from there."
},
"Keywords": {
"help": "One keyword per line, as `keyword = Destination name` — the destination exactly as it is named in Overflow's Tap module (or its id). Add ` @ Group A, Group B` to a line to switch only those groups instead of the ones chosen above. When a slide's notes carry one of these keywords, every chosen group is pointed at that destination; a slide without a keyword changes nothing. When several match, the first line wins."
"help": "An Overflow Dashboard login with Tap access — what Overflow's own ProPresenter helper app signs in with. Google sign-in does not work here: the account needs a password (Forgot Your Password? on the Dashboard sets one). The keywords, their destinations and their Tap groups are managed in the Dashboard under Integrations › ProPresenter › Keyword Mappings and read from there; after changing them, press the tile's refresh button or wait a few minutes."
},
"Alerts": {
"toggle": "alertEnabled",
@@ -55,53 +52,24 @@
"default": "",
"help": "Only if a password is set in ProPresenter's network preferences."
},
"clientId": {
"overflowEmail": {
"type": "string",
"label": "Client ID",
"label": "Email",
"group": "Overflow",
"default": ""
},
"apiKey": {
"overflowPassword": {
"type": "password",
"label": "API key",
"label": "Password",
"group": "Overflow",
"default": ""
},
"groups": {
"type": "multiselect",
"label": "Tap groups",
"nonprofitId": {
"type": "string",
"label": "Nonprofit ID",
"group": "Overflow",
"default": [],
"optionsRoute": "/groups",
"help": "The groups a keyword switches. None chosen means every group on the account."
},
"keywords": {
"type": "text",
"label": "Keywords",
"group": "Keywords",
"default": "",
"help": "One per line — give = Give Online"
},
"matching": {
"type": "select",
"label": "Match",
"group": "Keywords",
"default": "word",
"options": [
{
"value": "word",
"label": "Whole word in the notes"
},
{
"value": "anywhere",
"label": "Anywhere in the notes"
},
{
"value": "line",
"label": "On its own line"
}
],
"help": "Whole word keeps `give` from firing on `forgiveness`; anywhere is what Overflow's own helper does."
"help": "Leave blank: a church login carries its own. Only an @overflow.co staff login needs one."
},
"alertEnabled": {
"type": "switch",