Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
5b3b4d6
Enhance Azure Blob Storage support with SAS token handling improvemen…
JakobStadlhuber May 26, 2026
26827ec
Bump version to 1.2.11 in package.json and package-lock.json.
JakobStadlhuber May 26, 2026
c2e4c87
Add MP4 transcoding support, MIME type abstraction, and recording dur…
JakobStadlhuber May 26, 2026
f8a7397
Streamlined bot interactions, added more robust audio initialization …
JakobStadlhuber May 27, 2026
c5b6b91
Introduce lone participant exit delay, enhance inactivity detection, …
JakobStadlhuber May 27, 2026
569835a
Enhance Teams bot participant detection with improved parsing, meetin…
JakobStadlhuber May 27, 2026
cb575e3
Add meeting join failure notifications to Redis failure list; improve…
JakobStadlhuber May 27, 2026
3ff7777
Introduce processing queue for Redis jobs with atomic message handlin…
JakobStadlhuber May 27, 2026
feac07d
Improve handling of Google Meet lobby admission failures; add redirec…
JakobStadlhuber Jun 2, 2026
e923917
Enhance Teams bot meeting state detection with explicit empty meeting…
JakobStadlhuber Jun 2, 2026
26ca68b
Remove unused Linux X11 user agent in Chromium context and improve cl…
JakobStadlhuber Jun 2, 2026
6c3afd2
Add retry logic for Redis notifications with exponential backoff, imp…
JakobStadlhuber Jun 2, 2026
73f9801
Add Google Meet CDP options, Kubernetes sidecar support, display name…
JakobStadlhuber Jun 5, 2026
4e7dbf0
Add Chrome CDP service proxy
JakobStadlhuber Jun 5, 2026
636c48c
Rewrite Chrome CDP proxy host header
JakobStadlhuber Jun 5, 2026
dedcc3b
Keep Chrome CDP proxy running
JakobStadlhuber Jun 5, 2026
dcb6768
Use writable nginx temp paths for Chrome CDP
JakobStadlhuber Jun 5, 2026
0817d44
Add duration metadata to recording uploads
JakobStadlhuber Jun 9, 2026
b0be72d
Fix Google Meet CDP recording viewport
JakobStadlhuber Jun 10, 2026
2c89f6b
Fix Google Meet recording height
JakobStadlhuber Jun 10, 2026
4c92367
Improve recording codec handling
JakobStadlhuber Jun 10, 2026
c2cf80b
Track recording duration for Teams uploads
JakobStadlhuber Jun 15, 2026
a9665cf
fix: suppress Chrome first-run prompt
JakobStadlhuber Jun 15, 2026
611ae12
fix(google): make chrome-cdp sidecar work cross-container and add loc…
ddilushan Jun 17, 2026
4cde188
ci: run docker build on PRs (build-only) and parameterize chrome-cdp …
ddilushan Jun 17, 2026
346239a
feat(zoom): support external Chrome via CDP (ZOOM_CHROME_CDP_URL)
JakobStadlhuber Jun 18, 2026
a65c000
Merge PR #1: sync upstream screenappai/main (recording, queue, Teams …
JakobStadlhuber Jun 18, 2026
1d598ab
chore: bump version to 1.2.35
JakobStadlhuber Jun 18, 2026
c45d931
chore: bump version to 1.2.36
JakobStadlhuber Jun 18, 2026
afb2ec8
chore: bump version to 1.2.37
JakobStadlhuber Jun 18, 2026
8acc160
chore: bump version to 1.3.0
JakobStadlhuber Jun 18, 2026
0243c6b
fix(zoom): prefer VP8 + set video bitrate to fix laggy recordings
JakobStadlhuber Jun 18, 2026
4b71e1e
chore: bump version to 1.3.1
JakobStadlhuber Jun 18, 2026
d5b1fb8
fix(zoom): join Zoom web client over the chrome-cdp sidecar (swiftsha…
ddilushan Jun 25, 2026
80f42e2
chore: bump version to 1.3.2
JakobStadlhuber Jul 15, 2026
087709c
fix zoom CDP web join
JakobStadlhuber Jul 15, 2026
c8796a8
Merge pull request #2 from JakobStadlhuber/codex/fix-zoom-cdp-join
JakobStadlhuber Jul 15, 2026
df3bf06
feat(zoom): add configurable RTMS recording
JakobStadlhuber Jul 15, 2026
3a88183
fix(docker): load RTMS C++ runtime
JakobStadlhuber Jul 15, 2026
5bc56b9
Merge pull request #3 from JakobStadlhuber/codex/add-zoom-rtms
JakobStadlhuber Jul 15, 2026
902539e
fix(zoom): finalize and recover RTMS stop reasons
JakobStadlhuber Jul 15, 2026
0a5e77b
feat: add Chrome-first RTMS fallback
JakobStadlhuber Jul 16, 2026
bc3acc6
ci: install ffmpeg for media tests
JakobStadlhuber Jul 16, 2026
a2874ad
fix(zoom): handle prejoin media prompt
JakobStadlhuber Jul 16, 2026
5655656
fix(zoom): handle reusable prejoin overlay
JakobStadlhuber Jul 16, 2026
914b566
fix(zoom): handle sequential media prompts
JakobStadlhuber Jul 16, 2026
b233c4a
fix(chromium): prevent bot detection and bump version to 1.3.10
JakobStadlhuber Jul 16, 2026
ccf283c
fix(chromium): disable AutomationControlled to prevent bot detection
JakobStadlhuber Jul 16, 2026
c186ef5
fix(chromium): improve browser mimicry and add configurable storage s…
JakobStadlhuber Jul 16, 2026
fee54ae
feat(zoom): add customer-scoped Chrome profiles
JakobStadlhuber Jul 19, 2026
a8c0c68
fix(chromium): improve browser mimicry and add configurable storage s…
JakobStadlhuber Jul 19, 2026
a569671
feat(rtms): support internal and customer credentials
JakobStadlhuber Jul 19, 2026
04d3c62
feat(rtms): support shared and dedicated customer apps
JakobStadlhuber Jul 19, 2026
782d5ab
feat(rtms): enforce dedicated customer apps
JakobStadlhuber Jul 19, 2026
706198b
fix(core): skip placeholder legacy bot callbacks
JakobStadlhuber Aug 12, 2026
085a357
fix: reduce Google Meet recording load and release 1.3.16
JakobStadlhuber Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ JOIN_WAIT_TIME_MINUTES=2
# RETRY_COUNT=2 means: 1 initial attempt + 2 retries = 3 total attempts
RETRY_COUNT=2

# MediaRecorder target video bitrate (bits/sec) for Zoom tab-capture recordings.
# VP9 under software GL starved the encoder and produced ~187 kbps; this sets a sane target.
# RECORDING_VIDEO_BITS_PER_SECOND=4000000

# Optional Google Meet identity.
# Prefer a dedicated, consented Google account that is invited to meetings.
# If you use CDP, launch that Chrome with --auto-accept-this-tab-capture so
Expand All @@ -22,6 +26,40 @@ RETRY_COUNT=2
# Re-submit anonymous guest requests if Google redirects while still waiting for host admission.
GOOGLE_ANONYMOUS_JOIN_REQUEST_ATTEMPTS=10

# Optional: connect Zoom bots to an already-running Chrome via CDP.
# Launch that Chrome with --remote-debugging-port and the recording flags the
# Zoom bot needs (see recordingBrowserArgs in src/lib/chromium.ts). Every meeting
# gets a fresh isolated context, optionally seeded with its customer's state.
# ZOOM_CHROME_CDP_URL=http://host.docker.internal:9222
# Optional customer-specific Zoom session states (export via Playwright storageState).
# Values are read-only secret-mounted file paths selected by exact teamId.
# ZOOM_CHROME_CUSTOMER_STORAGE_STATE_PATHS_JSON={"team-id":"/var/run/secrets/meeting-bot/zoom-profiles/team-id.json"}

# Zoom transport: browser-only, browser with RTMS fallback, or forced RTMS.
# ZOOM_RECORDING_TRANSPORT=rtms forces RTMS and does not start Chrome for Zoom.
ZOOM_RECORDING_TRANSPORT=browser
# Allow RTMS only after an explicit pre-join automated-bot rejection.
ZOOM_RTMS_FALLBACK_ENABLED=false
# Exact teamId allowed to use the global/internal OAuth credentials below.
# ZOOM_RTMS_GLOBAL_TEAM_ID=internal-team-id
# Exact teamId allowlist for customer S2S OAuth credentials.
# Enforce shared or dedicated customer apps; auto keeps per-entry inference for compatibility.
ZOOM_RTMS_CUSTOMER_CREDENTIAL_MODE=auto
# ZOOM_RTMS_CUSTOMER_CREDENTIALS_JSON={"team-id":{"enabled":true,"accountId":"zoom-account-id","clientId":"zoom-s2s-client-id","clientSecret":"zoom-s2s-client-secret","participantUserId":"zoom-user-id"}}
# Add rtmsApp for a dedicated private General app. Its webhook URL is /zoom/rtms/webhook/apps/<webhookId>.
# ZOOM_RTMS_CUSTOMER_CREDENTIALS_JSON={"team-id":{"enabled":true,"accountId":"zoom-account-id","clientId":"zoom-s2s-client-id","clientSecret":"zoom-s2s-client-secret","participantUserId":"zoom-user-id","rtmsApp":{"webhookId":"unique-customer-app-id","clientId":"zoom-general-app-client-id","clientSecret":"zoom-general-app-client-secret","webhookSecret":"zoom-general-app-webhook-secret"}}}
# ZOOM_RTMS_CLIENT_ID=
# ZOOM_RTMS_CLIENT_SECRET=
# ZOOM_RTMS_WEBHOOK_SECRET=
# Prefer account-level S2S OAuth credentials. A short-lived access token is useful for local tests.
# ZOOM_RTMS_OAUTH_ACCESS_TOKEN=
# ZOOM_RTMS_OAUTH_ACCOUNT_ID=
# ZOOM_RTMS_OAUTH_CLIENT_ID=
# ZOOM_RTMS_OAUTH_CLIENT_SECRET=
# Required with account-level control when Zoom cannot infer the authorized participant.
# ZOOM_RTMS_PARTICIPANT_USER_ID=
# ZOOM_RTMS_EVENT_TTL_SECONDS=3600

# GCP bucket configuration for uploading debugging screenshots
GCP_ACCESS_KEY_ID=
GCP_SECRET_ACCESS_KEY=
Expand Down
71 changes: 62 additions & 9 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Install media test dependencies
run: |
sudo apt-get update
sudo apt-get install --yes ffmpeg

- name: Run tests
run: npm test

- name: Build application
run: npm run build

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand Down Expand Up @@ -58,15 +78,6 @@ jobs:
exit 1
fi

# Create and push git tag if it doesn't exist (only on main push)
- name: Create and push tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag ${{ env.TAG }}
git push origin ${{ env.TAG }}

# Always generate Docker tags for build, but only include version tag on main
- name: Extract metadata
id: meta
Expand All @@ -90,6 +101,39 @@ jobs:
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build Chrome CDP smoke image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.chrome-cdp
platforms: linux/amd64
load: true
tags: meeting-bot-chrome-cdp:smoke
cache-from: type=gha,scope=chrome-cdp

- name: Smoke-test Chrome CDP sidecar
run: |
set -euo pipefail
container=meeting-bot-chrome-cdp-smoke
cleanup() {
status=$?
trap - EXIT
if [ "$status" -ne 0 ]; then
docker logs "$container" || true
fi
docker rm -f "$container" >/dev/null 2>&1 || true
exit "$status"
}
trap cleanup EXIT

docker run --detach \
--name "$container" \
--network host \
--shm-size=1g \
--env CHROME_NO_SANDBOX=true \
meeting-bot-chrome-cdp:smoke
CHROME_CDP_CONTAINER="$container" node scripts/smoke-chrome-cdp.mjs

# Build and push on all branches
- name: Build and push Docker image
uses: docker/build-push-action@v5
Expand All @@ -116,3 +160,12 @@ jobs:
labels: ${{ steps.chrome-cdp-meta.outputs.labels }}
cache-from: type=gha,scope=chrome-cdp
cache-to: type=gha,mode=max,scope=chrome-cdp

# Create and push the release tag only after both images and the smoke test succeed.
- name: Create and push tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag ${{ env.TAG }}
git push origin ${{ env.TAG }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ assets/videos
*.wav
__pycache__/
.env
.env.*
!.env.example
.chrome/
gcp-key
assets/screenshots
/data
debug-videos/
.env.native

# Local-only docker compose override for signal-handling testing — see compose.override.yml header
compose.override.yml
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security
- N/A

## [1.3.16] - 2026-09-09

### Fixed
- Reduce Google Meet recording load by preferring VP8 for WebM and limiting capture to 25 fps; honor the existing video bitrate setting.

## [1.0.0] - 2024-01-01

### Added
Expand Down
6 changes: 3 additions & 3 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ The workflow is defined in `.github/workflows/docker-build.yml` and includes:

### Production Image (`Dockerfile.production`)

- **Base**: Node.js 20 Alpine
- **Base**: Node.js 22 Bookworm
- **Multi-stage build** for smaller final image
- **Security**: Runs as non-root user
- **Optimized**: Only production dependencies
- **Health check**: Built-in health monitoring

### Development Image (`Dockerfile`)

- **Base**: Node.js 20
- **Base**: Node.js 22 Bookworm
- **Full dependencies** for development
- **Hot reload** support
- **Debugging tools** included
Expand Down Expand Up @@ -269,4 +269,4 @@ For high availability, consider:

### Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to get involved with the project.
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to get involved with the project.
5 changes: 4 additions & 1 deletion Dockerfile.chrome-cdp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ RUN apt-get update && \
dumb-init \
ffmpeg \
fonts-liberation \
fonts-noto-cjk \
fonts-noto-color-emoji \
fonts-noto-core \
gnupg \
libasound2 \
libatk-bridge2.0-0 \
Expand Down Expand Up @@ -54,7 +57,7 @@ RUN chmod +x /usr/local/bin/start-chrome-cdp

USER chrome
ENV XDG_RUNTIME_DIR=/run/user/1001
EXPOSE 9222 9223
EXPOSE 9223

ENTRYPOINT ["dumb-init", "--"]
CMD ["start-chrome-cdp"]
15 changes: 13 additions & 2 deletions Dockerfile.development
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
FROM node:20
FROM gcc:13-bookworm AS rtms-cxx-runtime
RUN cp "$(g++ -print-file-name=libstdc++.so.6)" /libstdc++.so.6

FROM node:22-bookworm

COPY --from=rtms-cxx-runtime /libstdc++.so.6 /opt/rtms/libstdc++.so.6
ENV LD_LIBRARY_PATH=/opt/rtms

WORKDIR /usr/src/app

Expand Down Expand Up @@ -57,14 +63,19 @@ RUN npm install -g nodemon
COPY package*.json ./
RUN npm install

RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \
cp /opt/rtms/libstdc++.so.6 /usr/src/app/node_modules/@zoom/rtms/build/Release/libstdc++.so.6 && \
node -e "const rtms=require('@zoom/rtms').default;if(typeof rtms.Client!=='function')process.exit(1)"; \
fi

# Install Playwright and its dependencies
RUN npx playwright install --with-deps

# Copy application code
COPY . .

# Build app
RUN npm run build
RUN npm test && npm run build

# Set permissions
RUN chmod +x /usr/src/app/start.sh
Expand Down
16 changes: 14 additions & 2 deletions Dockerfile.production
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
FROM gcc:13-bookworm AS rtms-cxx-runtime
RUN cp "$(g++ -print-file-name=libstdc++.so.6)" /libstdc++.so.6

# Use the official Node.js image as a base image
FROM node:20
FROM node:22-bookworm

COPY --from=rtms-cxx-runtime /libstdc++.so.6 /opt/rtms/libstdc++.so.6
ENV LD_LIBRARY_PATH=/opt/rtms

# Set the working directory
WORKDIR /usr/src/app
Expand Down Expand Up @@ -63,6 +69,12 @@ COPY package*.json ./
# Install all dependencies (including dev dependencies for build)
RUN npm ci && npm cache clean --force

# The Zoom RTMS Linux SDK requires GCC 13's C++ runtime and currently supports amd64 only.
RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \
cp /opt/rtms/libstdc++.so.6 /usr/src/app/node_modules/@zoom/rtms/build/Release/libstdc++.so.6 && \
node -e "const rtms=require('@zoom/rtms').default;if(typeof rtms.Client!=='function')process.exit(1)"; \
fi

# Install Playwright and its dependencies
RUN npx playwright install --with-deps

Expand All @@ -77,7 +89,7 @@ RUN dos2unix /usr/src/app/xvfb-run-wrapper && \
chmod +x /usr/src/app/xvfb-run-wrapper

# Build the TypeScript code
RUN npm run build
RUN npm test && npm run build

USER root
RUN mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix
Expand Down
Loading