Collaboration Central, A bot for mass sending of messages to associates.
Find a file
Joseph B. McQueen 524b443dce Make favorite groups editable from the compose page
Selecting a group in "Additional groups" now auto-saves it to the
caller's favorites, and each favorite gets a × affordance for one-click
removal. Favorites still live where they always have —
config.webex.bot[app].authorized[personId].groups — so nothing changes
for existing installations.

Backend
- New POST /CollabCentral/:app/user/groups/add. Body { id }. Validates
  the caller is authorized for :app, validates :id resolves to a real
  Webex group by looking it up in the cached org-wide group list
  (blocks arbitrary strings from being stuffed into config.json),
  dedups against the existing favorites array, writes config.json via
  saveConfig, and returns the updated array.
- New POST /CollabCentral/:app/user/groups/remove. Body { id }.
  Filters that id out of the caller's favorites, writes only when
  something actually changed (a remove of an unknown id no-ops instead
  of rewriting config.json), and returns the updated array.
- Both endpoints are safe against concurrent writes: index.js is
  single-process and node is single-threaded, so read/mutate/write
  runs atomically per request.
- Both log a compact audit line (last-8 of personId + group name) so
  operators can see who is curating what.

Frontend (sendMessage.html + .js + app.css)
- New "Manage favorites" chip strip renders directly under the
  Favorite Groups picker. Each favorite becomes a pill (uses .alias
  when set, otherwise .name; long labels truncate with ellipsis).
  Clicking × on a pill removes that favorite server-side and updates
  the strip locally.
- Additional Groups picker wires a 'change' listener that diffs the
  current selection against the previous one and only fires
  /user/groups/add for newly-picked ids (never re-fires on the
  reselect side of a deselect+reselect, never spams the API with the
  full selection on every keystroke).
- Client keeps favoriteGroups mirrored to every API response so the
  "already a favorite?" dedup check is a pure in-memory lookup — no
  wasted round trips when the user re-picks a group they already
  favorited.
- Transient status message ("Added to favorites." / "Removed from
  favorites." / error variants) fades under the field label; sticks
  around ~4s.
- New shared styles: .fieldLabelRow (label + inline status),
  .fieldStatus (with --error variant), .chipStrip, .chip,
  .chip__label, .chip__close (with hover/focus states).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 20:25:15 -04:00
config Phase 9: rebuild frontend on a shared layout + fix Monitor Jobs race 2026-07-01 19:36:36 -04:00
html Make favorite groups editable from the compose page 2026-07-01 20:25:15 -04:00
lib Phase 8: extract pure helpers into lib/ and cover with node:test 2026-07-01 18:21:02 -04:00
test Phase 8: extract pure helpers into lib/ and cover with node:test 2026-07-01 18:21:02 -04:00
uploads Initial commit: multi-bot CollabCentral 2026-07-01 17:53:07 -04:00
.dockerignore Initial commit: multi-bot CollabCentral 2026-07-01 17:53:07 -04:00
.env.example Phase 7: polish for production readiness 2026-07-01 18:00:59 -04:00
.gitignore Initial commit: multi-bot CollabCentral 2026-07-01 17:53:07 -04:00
Dockerfile Phase 7: polish for production readiness 2026-07-01 18:00:59 -04:00
index.js Make favorite groups editable from the compose page 2026-07-01 20:25:15 -04:00
package-lock.json Initial commit: multi-bot CollabCentral 2026-07-01 17:53:07 -04:00
package.json Phase 8: extract pure helpers into lib/ and cover with node:test 2026-07-01 18:21:02 -04:00
README.md Phase 8: extract pure helpers into lib/ and cover with node:test 2026-07-01 18:21:02 -04:00

CollabCentral

A multi-bot mass-messaging service for Webex. Each bot has its own token, avatar, label, and per-user access list, and users can only see the bots and jobs they're authorized on.

Under one server you can host any number of bots (Novi, TechUpdates, …) — each one exposes the same UI at:

  • /CollabCentral/<botName>/sendMessage.html — compose and send.
  • /CollabCentral/<botName>/monitorJobs.html — track running / scheduled / completed jobs.
  • /CollabCentral/<botName>/jobDetail.html?jobId=… — detailed per-job view.

Requirements

  • Node.js 20+.
  • A single Webex integration used for OAuth on all bots.
  • A Webex service account (used server-side for group/people lookups; its refresh token is stored in config/token.json and rotated automatically).
  • One Webex bot per app you want to host.

Quick start (local)

  1. Copy .env.example to .env and fill in the values:

    SERVER_PORT=1450
    CRON_TIMEZONE=America/New_York
    OAUTH_CALLBACK_URL_TEMPLATE=https://bot.example.com/CollabCentral/:app/oauth
    WEBEX_INTEGRATION_CLIENT_ID=...
    WEBEX_INTEGRATION_CLIENT_SECRET=...
    WEBEX_SERVICE_ACCOUNT_CLIENT_ID=...
    WEBEX_SERVICE_ACCOUNT_CLIENT_SECRET=...
    GOOGLE_TRANSLATE_API_KEY=...
    
  2. Copy config/botTokens.example.json to config/botTokens.json and put in the bot tokens for each app:

    {
        "novi":        { "token": "…", "enabled": true },
        "techupdates": { "token": "…", "enabled": true }
    }
    
  3. Create config/token.json with the initial service-account OAuth tokens (access + refresh). After the first refresh, the cron job rewrites this file automatically.

  4. Install dependencies and start:

    npm install
    npm start
    

npm start uses node --env-file=.env, so no external process manager is required to load env vars during local development.

Adding a new bot

Say you want to add a bot called alerts.

  1. Webex bot — create the bot at developer.webex.com. Copy its access token.
  2. Bot token — add an entry to config/botTokens.json:
    "alerts": { "token": "<bot access token>", "enabled": true }
    
    Setting enabled: false will keep the bot listed but return 404 for all /CollabCentral/alerts/* routes.
  3. Bot metadata + authorized users — edit config/config.json and add an entry under webex.bot:
    "alerts": {
        "label": "Ops Alerts",
        "authorized": {
            "<webexPersonId>": {
                "id": "<webexPersonId>",
                "displayName": "…",
                "email": "…",
                "avatar": "…",
                "groups": [ { "name": "…", "alias": "…", "id": "<webexGroupId>" } ]
            }
        }
    }
    
    Only person IDs listed under authorized can log into that bot's UI.
  4. Icons — drop alerts.png and alerts.ico into html/ (they're served by the per-bot static mount).
  5. OAuth redirect URI — in the Webex integration used for OAuth, register https://<your-host>/CollabCentral/alerts/oauth as an allowed redirect URI. Without this step, users get an OAuth error on login.
  6. Restart the server (or the container). The bot's avatar is fetched once at startup via Webex /people/me using the token you supplied in step 2.

Docker

Dockerfile builds a stateless image. Config, tokens, uploads, and jobs data are expected to be mounted at runtime.

docker build -t collabcentral .
docker run -d \
    --name collabcentral \
    --env-file /path/to/.env \
    -v /path/to/config:/usr/src/app/config \
    -v /path/to/uploads:/usr/src/app/uploads \
    -p 1450:1450 \
    collabcentral

The config/ mount should contain at minimum config.json, botTokens.json, token.json, languages.json, and (if you want to preserve state) jobs.json and userPrefs.json.

File layout

Committed to the repo:

  • index.js — Express server, OAuth, cron jobs, Webex API integration, and the send queue.
  • html/ — frontend (sendMessage / monitorJobs / jobDetail pages, per-bot icons, shared JS libraries).
  • config/config.json — server settings, per-bot labels, and the authorized-user table for each bot.
  • config/languages.json — supported translation languages.
  • config/botTokens.example.json — template for botTokens.json.
  • .env.example — template for .env.
  • Dockerfile, .dockerignore, .gitignore, package.json, package-lock.json.

Runtime state (gitignored, not committed):

  • .env — secrets and deployment-specific URLs.
  • config/botTokens.json — per-bot access tokens.
  • config/token.json — service-account OAuth tokens (rotated by the refresh cron).
  • config/jobs.json — building / running / scheduled / completed jobs.
  • config/userPrefs.json — per-user preferences (language, etc.).
  • uploads/ — CSVs of recipient IDs and any attached images.

Testing

Unit tests for the pure helpers extracted into lib/helpers.js run under Node's built-in test runner — no additional dev dependencies required.

npm test

The suite covers bot enable/disable gating (getBotToken, isBotEnabled, getBotConfig), per-bot draft isolation (buildingKey, jobsForApp), the authorization check (isAuthorized), OAuth URL construction (buildAuthUrl, getOAuthRedirectUri), the retention filter (cleanCompletedJobs), and the duration formatter (msToTime). Adding a new helper? Add it to lib/helpers.js and cover it in test/helpers.test.js — keeping the state-carrying wrappers in index.js thin means each helper can be tested without booting the server.

Behavior notes

  • Completed-job retention: the daily cleanup cron (default 01:10 in CRON_TIMEZONE) drops entries from jobs.completed older than 30 days. Change COMPLETED_RETENTION_DAYS in index.js to adjust.
  • Send concurrency: a shared p-queue limits outbound Webex sends to 10 in-flight requests.
  • Rate limiting: fetchWithRateLimit transparently retries on Webex 429 responses honoring Retry-After.
  • Per-bot job isolation: draft jobs are keyed by cookieId + appName, and job list / detail endpoints filter by appName, so authorized users of one bot never see another bot's jobs.