# wbxStoreProvision Webex Calling provisioning bot for AEO retail stores. Runs as a Webex bot and exposes slash commands that create Webex locations, attach phone numbers, upload greetings, build auto-attendants, and clean up user licensing. ## Requirements - Node.js 20+ (Docker image uses `node:22-alpine`). - A Webex bot token, a Webex integration (service account) with the scopes currently used by admin API calls, a Twilio lookup account, an SIW basic-auth user, and a Google API key with Address Validation + Time Zone enabled. - An on-prem host that can reach Store Info Web, to run the remote agent (see [Remote SIW agent](#remote-siw-agent) below). - Optional: a Google service-account JSON key. Only the REST API key is required today, but if/when you add code that uses `google-auth-library`, save the JSON at `config/google-service-account.json` and set `GOOGLE_APPLICATION_CREDENTIALS` in `.env` to point at it. The file is git-ignored. ## Local setup ```bash cp .env.example .env # Edit .env with real values npm install npm start ``` The token-refresh cron reads and writes `config/wbxTokens.json` (path configurable via `WEBEX_TOKEN_STORE`). Seed this file once with a valid `access_token`, `refresh_token`, and `expiresOn`; the process will keep it up to date from then on. ## Scripts | npm script | What it does | | -------------------------- | ----------------------------------------------------------- | | `npm start` | Run the bot. | | `npm run dev` | Run with `node --watch` for local iteration. | | `npm run lint` | ESLint (flat config) across the tree. | | `npm run format` | Prettier write. | | `npm run generate-911-csv` | Rebuild `buildingFile.csv` + `locationFile.csv` from SIW. | | `npm run fix-store-phones` | Normalize licensing + default meeting site for store users. | ## Docker ```bash docker build -t wbxcallprov . docker run --rm --env-file .env \ -p 8080:8080 \ -v "$(pwd)/config:/app/config" \ wbxcallprov ``` The volume mount preserves `config/wbxTokens.json` across restarts so the refresh cron doesn't lose state. The `-p 8080:8080` publishes the WebSocket port so the remote SIW agent can connect back to the bot. ## Remote SIW agent Store Info Web only accepts connections from inside the corporate network, but the bot runs in the cloud. To bridge the gap, the bot hosts a WebSocket server; a small on-prem agent dials in and proxies HTTP requests back and forth. The agent itself is intentionally generic — it just proxies whatever `{method, url, headers, auth, body}` payload arrives — and lives in [`docker/remote-agent/`](docker/remote-agent) as a self-contained Docker bundle you can build here and ship to the on-prem host. Once the agent is connected, the bot logs `Remote agent connected` and any `/buildStore`, `/stageStore`, `/migrateStore` command will succeed. If the agent is not connected, the SIW-dependent commands fail immediately with `No remote SIW agent connected` rather than silently timing out. ### Deploying the agent The `docker/remote-agent/` folder produces a fully offline-installable ZIP (image tarball + `install.sh` + `docker-compose.yml`). The workflow is the same as netanalyzer's bundle — same layout, same operator playbook — and the two bundles use distinct image tags and container names (`wbxprov-remote-agent` vs `sha-remote-agent`) so a single on-prem host can run both agents side by side. Build the bundle (on your workstation, or in CI): ```bash # Default: linux/amd64 (typical Rocky/RHEL/Ubuntu server) npm run agent:package # Or explicitly, with a different target arch: ./docker/remote-agent/package.sh --platform linux/arm64 ``` That produces `docker/remote-agent/dist/wbxprov-remote-agent-.zip`. Transfer it to the on-prem host, then: ```bash unzip wbxprov-remote-agent-.zip cd wbxprov-remote-agent- ./install.sh # first run: seeds .env and stops vi .env # set WS_URL + WS_TOKEN ./install.sh # second run: starts the container docker compose logs -f # tail the agent's connection status ``` Set `WS_URL` to the bot's public WebSocket endpoint (e.g. `wss://your-wbxstoreprovision-host:8080`) and `WS_TOKEN` to the same value you configured for `WS_TOKEN` in the bot's `.env`. See [`docker/remote-agent/README.md`](docker/remote-agent/README.md) for build details and [`docker/remote-agent/deploy/README.md`](docker/remote-agent/deploy/README.md) for the full operator guide (upgrades, troubleshooting, coexistence with `sha-remote-agent`). ## Bot commands Registered in [src/commands](src/commands): - `/buildStore ` — full green-field build (create location, calling, greeting, attach user, license cleanup). - `/stageStore ` — pre-migration setup: same as buildStore but without phone-number attachment or licensing cleanup. - `/migrateStore ` — cut-over for a staged store: attach the phone number, set caller ID, create the auto-attendant, finalize licensing. - `/storeinfo ` — show current Webex info for the store user (`ae<5-digit>@ae.com`). - `/userinfo ` — show current Webex info for any user by email. - `/help` — bot's own help output. Card confirmations post `attachmentAction` events, dispatched in [src/commands/attachmentActions.js](src/commands/attachmentActions.js). ## Architecture ``` src/ index.js bot bootstrap, cron, command wiring (~70 lines) config.js dotenv loading + validation constants.js org-scoped IDs (route groups, licenses, greetings, ...) logger.js small level-aware logger http.js fetch wrapper (rate limit + optional SIW TLS agent) webex/ all Webex API calls, one module per resource family integrations/ SIW / Twilio / Google cards/ Adaptive Card builders flows/ multi-step provisioning (build, stage, migrate, ...) commands/ framework.hears handlers + attachmentAction dispatch scripts/ one-off maintenance scripts (911 CSV, phone fix-up) greetings/ WAV files uploaded as location announcements config/wbxTokens.json persistent service-account token cache (git-ignored) ``` Data flow for a store provisioning: ```mermaid flowchart LR User[Webex user] -->|/buildStore 1234| Commands[commands/*] Commands --> SIW[integrations/siw.js] Commands --> Users[webex/users.js] Commands --> Card[cards/storeInfoCard.js] Card -->|confirm| Attachment[attachmentActions.js] Attachment --> Flow[flows/buildStore.js] Flow --> Locations[webex/locations.js] Flow --> Devices[webex/devices.js] Flow --> Announce[webex/announcements.js] Flow --> License[webex/licensing.js] subgraph background Cron[node-cron every 60s] --> Auth[webex/auth.js] Auth --> Tokens[(config/wbxTokens.json)] end ``` ## Security notes - `.env` and `config/wbxTokens.json` are git-ignored. Never commit them. - `NODE_TLS_REJECT_UNAUTHORIZED=0` is no longer set globally. If SIW's TLS cert cannot be verified from your host, set `ALLOW_INSECURE_SIW_TLS=true`; the insecure dispatcher is then scoped to SIW requests only. - Concurrent Webex token refreshes are collapsed into a single in-flight refresh in [src/webex/auth.js](src/webex/auth.js). ### One-time secret rotation The pre-refactor `config.json` and `getMeeting.js` stored real secrets in plain text on disk. Regardless of git history, treat the following as **exposed** and rotate them at their source: - Webex bot token (`WEBEX_BOT_TOKEN`) - Webex integration client secret + service-account access / refresh tokens (`WEBEX_SVC_CLIENT_SECRET`, plus everything in `config/wbxTokens.json`) - Twilio auth token (`TWILIO_AUTH_TOKEN`) - SIW basic-auth password (`SIW_PASSWORD`) - Google API key (`GOOGLE_API_KEY`) and any service-account private key that was previously in `config.json` After rotating, populate the new values in `.env` and seed a fresh `config/wbxTokens.json` with the new access/refresh token pair.