Final Release v1.1.0 — ZCC, proxy, transcript params, individual video, Python asyncio/executor#109
Merged
MaxMansfield merged 69 commits intomainfrom Apr 16, 2026
Merged
Final Release v1.1.0 — ZCC, proxy, transcript params, individual video, Python asyncio/executor#109MaxMansfield merged 69 commits intomainfrom
MaxMansfield merged 69 commits intomainfrom
Conversation
Advances both Node.js (package.json, package-lock.json) and Python (pyproject.toml) to 1.1.0 in preparation for the v1.1 release.
Bump brace-expansion 2.0.2→2.0.3, markdown-it 14.1.0→14.1.1, picomatch 2.3.1→2.3.2 and 4.0.3→4.0.4, minimatch 9.0.5→9.0.9, tar 7.5.7→7.5.13, yaml 2.8.2→2.8.3.
- Pygments: 2.19.2 → 2.20.0 (fixes CVE-2026-4539) - Add requirements-dev.txt pinning build/test/publish deps with Pygments>=2.20.0 to prevent regression
Keep the internal v1.1 spec out of the public repository.
Replace rtms_csdk.h with the new rtms_sdk.h C++ SDK. Client now inherits rtms_sdk_sink and implements callbacks as virtual overrides, eliminating the static sdk_registry_, registry_mutex_, and all nine static handleXxx functions. Provider factory (rtms_sdk_provider) replaces rtms_alloc/rtms_init/rtms_uninit/rtms_release. join() now calls sdk_->open(this) instead of rtms_set_callbacks(). CMakeLists updated to search for rtms_sdk.h instead of rtms_csdk.h.
Add RTMS_BUILD_TESTS=ON cmake option that wires up a Catch2 test target (via FetchContent) linked against tests/mock_sdk.cpp instead of the real Zoom SDK binary. The library requirement is bypassed for test-only builds. Also bump cmake project version to 1.1.0 and add task test:cpp to Taskfile.yml.
Add a stub implementation of rtms_sdk and rtms_sdk_provider that replaces the Zoom SDK binary for unit testing. MockSdkState provides per-test configurable return values and call recording; mock_trigger_* helpers simulate SDK callbacks firing on the Client sink. 47 Catch2 tests cover: AudioParams/VideoParams/DeskshareParams validation, Session null-pointer safety, MediaParams composition and toNative(), Client lifecycle (create/join/poll/release), callback dispatch with guard conditions (null buf, zero size, empty string), event subscription deferral and on-confirm flush, and media type auto-enable on callback registration. Runs in CI with no credentials or SDK binary required via task test:cpp.
ZCC (Zoom Contact Center) uses engagement_id as its session identifier instead of meeting_uuid. The Python test class TestZccEngagementId adds four failing tests that verify: - join(engagement_id=...) returns True (not False from swallowed error) - _do_join() does not raise the missing-identifier ValueError - engagement_id is forwarded as the first positional arg to native join() - meeting_uuid takes priority when both identifiers are supplied The JS test documents the expected API contract (the fully-mocked test suite can't exercise real routing logic, so the Python tests carry the behavioral coverage).
ZCC (Zoom Contact Center) identifies sessions with an engagement_id rather than a meeting_uuid. This adds engagement_id to the JoinParams interface and join() routing logic in all binding layers so ZCC callers can pass engagement_id without falling back to an error. Priority order: meeting_uuid > webinar_uuid > session_id > engagement_id. The C++ core join() signature is unchanged — engagement_id is forwarded as the meeting_uuid positional arg to sdk_->join().
Adds tests across all three layers that fail before implementation: - C++: TranscriptParams default values, setters, toNative() mapping, MediaParams::setTranscriptParams/hasTranscriptParams, and Client::setTranscriptParams() triggering reconfigure - Python: TranscriptParams class existence, defaults, setters, LANGUAGE_ID constants, set_transcript_params() on Client, __all__ exports - JS: mock wired for setTranscriptParams and TranscriptParams; LANGUAGE_ID_ENGLISH/NONE constant values documented
Adds transcript parameter configuration to the C++ core, Node.js, and Python bindings. TranscriptParams extends BaseMediaParams with src_language (default -1/auto-detect) and enable_lid (default true). TranscriptLanguage exposes all 38 language IDs as a named dict/object, following the same pattern as AudioCodec and VideoCodec.
Adds 16 C++ enums (175 members) to rtms.h as the single source of truth for all protocol constants, sourced from the Zoom RTMS data types docs. Updates node.cpp and python.cpp to reference enum values instead of raw integers throughout. Key fixes: - MediaType::ALL corrected from 31 to 32 (SDK_ALL = 0x1<<5) - StopReason expanded from 19 to 27 values; fixes STOP_BC_HOST_DISABLED_APP (was APP_DISABLED_BY_HOST) and STOP_BC_INSTANCE_CONNECTION_INTERRUPTED (was MEETING_*) - MessageType expanded from 19 to 30 values - StreamState gains PAUSED=5 and RESUMED=6 - EventType gains PARTICIPANT_VIDEO_ON/OFF; ZCC voice events use separate ZccVoiceEventType enum - Adds TranscriptLanguage dict and setTranscriptParams to node.cpp (parity with python.cpp) - All pybind11 dict assignments use explicit (int) casts to avoid unregistered enum type errors
NVM and pyenv were unconditionally installed in every image build regardless of TARGET, adding unnecessary time to py-only and js-only builds. Moved both installs inside TARGET conditionals. Consolidates Python dev dependencies into requirements-dev.txt (adds auditwheel) so the Dockerfile uses a single pip install -r instead of a hardcoded package list. Updates test:py (Taskfile) to use .venv on darwin, and the test-py Docker service to use system pip/pytest (no venv needed in container).
Enables proper TypeScript type checking and syntax in test files (rtms.test.ts, rtms.wrapper.test.ts) via ts-jest transformer, configured in jest.config.cjs.
Adds rtms.wrapper.test.ts which spawns real node processes against the built ESM module to verify the actual compiled output — bypassing the jest.mock() approach in rtms.test.ts that prevents genuine red/green TDD cycles for new methods. Adds jest.config.cjs (required because package.json has "type":"module") to configure ts-jest for .ts test files and babel-jest for .js files. Updates Taskfile test:js to run both test files.
Groups test files by language for clarity as the test suite grows. Updates all path references in CMakeLists.txt, Taskfile.yml, and compose.yaml to match the new layout.
Adds setProxy(proxy_type, proxy_url) to the C++ Client class, delegating to the SDK's set_proxy() and throwing rtms::Exception on failure. This is the shared implementation that all language bindings delegate to.
Adds NodeClient::setProxy() in node.cpp with argument validation, a passthrough in index.ts, and full JSDoc + type declaration in rtms.d.ts.
Adds setProxy() to PyClient in python.cpp, set_proxy() as the primary snake_case API in __init__.py (with setProxy as a legacy camelCase alias), and type stubs in __init__.pyi.
Adds 3 Catch2 unit tests covering proxy forwarding, https support, and SDK failure handling. Adds 1 pack-install integration test verifying setProxy is present on the installed Client instance. Also removes stray console.log from pack-install beforeAll.
Registers snake_case names as primary on the native pybind11 layer (e.g. set_proxy, on_audio_data, subscribe_event) alongside the existing camelCase names retained as legacy aliases. Updates the Python wrapper to call super() via snake_case and renames all wrapper-defined methods to snake_case with camelCase aliases. Updates type stubs throughout. This makes the Python SDK idiomatic (PEP 8) while preserving full backward compatibility for callers using the camelCase API.
…o callbacks Red phase for feat/individual-video (branch 5 of v1.1). Covers all four test layers — C++ (subscribeVideo, setOnParticipantVideo, setOnVideoSubscribed), TypeScript mock, TypeScript wrapper (real built module), and Python — with 15 Python, 5 JS wrapper, and 12 C++ compile-error failures confirming the feature is unimplemented. Also adds mock_trigger_participant_video and mock_trigger_video_subscript_resp trigger helpers to the C++ mock so behavioral tests can fire SDK sink callbacks once the implementation exists.
Move detailed API examples (webhook validation, asyncio, executor dispatch, scaling strategy) from README into dedicated language guides (examples/node.md, examples/python.md). Add Zoom Contact Center guide (examples/zcc.md). README is now a concise entry point with links to in-depth per-language references.
The C SDK's rtms_metadata struct carries startTs, endTs, and an ai_interpreter sub-struct (source language, sample rate, and up to 100 target language entries with voice/engine details) that were previously silently dropped at the C++ wrapper boundary. Adds AiTargetLanguage and AiInterpreter C++ classes, extends Metadata to surface all four new fields, and propagates them through both the Node.js (buildMetadataObj helper) and Python (new pybind11 class registrations) bindings, including TypeScript interfaces and Python type stubs.
When the C SDK fires audio/transcript callbacks without populating the ai_interpreter struct, target_size is uninitialized and often negative. The previous check (< 100) passed negative values through, which then converted to a huge size_t in reserve(), throwing std::length_error and crashing both Node.js and Python bindings. Require target_size > 0 before trusting it; default to 0 targets.
…efaults The C++ SDK's config() hangs when called before open() for VIDEO streams. Introduced sdk_opened_ flag so configure() stores params but defers the actual sdk_->config() call until join() has called sdk_->open(). join() now calls configure() unconditionally after open (not gated on !media_params_updated_) to ensure pre-join setOn*/setVideoParams calls are applied. VideoParams() and DeskshareParams() default constructors now use RAW_VIDEO / H264 / HD / 30fps / VIDEO_SINGLE_ACTIVE_STREAM instead of all-zeros, enabling partial setVideoParams() calls (e.g. dataOpt only) without triggering SDK errors or validation failures. Tests updated to reflect deferred-config behavior (config_calls == 0 before join()) and corrected VideoParams default field values.
ParticipantInfo exposes id and name, not userId and userName. The individual video subscribe example was using the wrong field names.
All RTMS_-prefixed enum class names inside the rtms namespace had a
redundant prefix (e.g. rtms::RTMS_EVENT_TYPE). Renamed to match the
ALL_CAPS convention of non-prefixed enums already in the file:
RTMS_SESSION_STATE → SESSION_STATE
RTMS_STREAM_STATE → STREAM_STATE
RTMS_EVENT_TYPE → EVENT_TYPE
RTMS_ZCC_VOICE_EVENT_TYPE → ZCC_VOICE_EVENT_TYPE
RTMS_MESSAGE_TYPE → MESSAGE_TYPE
RTMS_STOP_REASON → STOP_REASON
RTMS_TRANSCRIPT_LANGUAGE → TRANSCRIPT_LANGUAGE
Updated all references in src/rtms.{h,cpp}, src/node.cpp, src/python.cpp,
and the Python wrapper comments.
…pantVideo Without calling subscribeEvent(), the SDK never delivered PARTICIPANT_VIDEO_ON or PARTICIPANT_VIDEO_OFF events, so onParticipantVideo callbacks never fired. Mirrors the same pattern used by setOnUserUpdate for JOIN/LEAVE/ACTIVE_SPEAKER.
…ode and Python refs
Both docs were missing setVideoParams/setAudioParams/setDeskshareParams coverage.
The individual video streams section in node.md also omitted the required
setVideoParams({ dataOpt: VIDEO_SINGLE_INDIVIDUAL_STREAM }) prerequisite call.
Node.js additions:
- New "Media Configuration" section with Video/Audio/Desktop Share subsections
- Updated "Individual Video Streams" to show setVideoParams as a required first step
Python additions:
- New "Media Callbacks" section showing all four callbacks and on_active_speaker_event
- New "Media Configuration" section with Video/Audio/Desktop Share/Transcript subsections
- New "Individual Video Streams" section mirroring the Node.js equivalent
…utes Expose EVENT_PARTICIPANT_VIDEO_ON and EVENT_PARTICIPANT_VIDEO_OFF as top-level module attributes in both Node.js and Python bindings, matching the pattern already used for EVENT_CONSUMER_ANSWERED and similar constants. Also fix a NameError in Python's _wrap_callback: _run_executor was referenced but never defined; initialise it to None at module level and wire the executor kwarg through run() / run_async() so the global fallback executor actually works.
C++ tests (70/70): - Fix compile error: Client::EVENT_PARTICIPANT_JOIN was never valid; replace with (int)EVENT_TYPE::PARTICIPANT_JOIN - Fix setTranscriptParams test: configure() is deferred until after join/open, so call join() before checking config_calls - Add setOnParticipantVideo auto-subscribe test - Add Metadata start_ts/end_ts construction tests - Add AiInterpreter target_size bounds-guarding tests Python tests (122/122): - Fix codec membership checks: use __members__ for enum name lookup - Update test_client_has_polling_control: check EventLoop attributes (_do_alloc_and_join, _pending_join_params) instead of removed methods - Update ZCC engagement_id tests: use _do_alloc_and_join() + pending params dict instead of the removed _do_join() / _start_polling API - Add EVENT_PARTICIPANT_VIDEO_ON/OFF constant assertions - Add individual video method and callback tests Node.js wrapper tests (98/98): - Add onUserUpdate to the callback method list
Covers all dev branch changes: ZCC engagement_id, TranscriptParams, setProxy, individual video streams, Python EventLoop/EventLoopPool, run_async, executor dispatch, async callbacks, GIL release, lazy alloc, AiInterpreter metadata fields, RTMS_ enum prefix removal, and test suite expansion (70 C++ / 122 Python / 98 Node.js tests).
stopCallbacks() replaces each media callback with an empty lambda, which triggered setOnAudioData/Video/Transcript/Deskshare → updateMediaConfiguration → configure() on a session that had already been torn down. This printed four "Warning: Failed to update media configuration: configure failed" lines every time a meeting ended. Two guards added: 1. updateMediaConfiguration now checks sdk_opened_ before calling configure(), matching the guard already present in configure() itself. 2. release() resets sdk_opened_ = false before release_sdk() so any configure path entered after leave() is silently short-circuited.
fix(core): suppress spurious configure warnings on leave
Three changes: - Node.js tests: tests/rtms.test.ts → tests/ts/rtms.test.ts + rtms.wrapper.test.ts (tests were reorganised into ts/ subdirectory; wrapper tests were never run in CI) - Node.js integration: tests/pack-install.test.js → tests/ts/pack-install.test.js - Python tests: tests/test_rtms.py → tests/py/test_rtms.py Add test-cpp-linux and test-cpp-macos jobs that build the mock-SDK C++ test suite (RTMS_BUILD_TESTS=ON) and run it via ctest. No SDK binary required — Catch2 is fetched via FetchContent and mock_sdk.cpp replaces the real Zoom SDK. C++ jobs are added to the needs lists for check-version-change and build-docs so CI only proceeds when all 3 languages pass.
ci: fix test paths and add C++ unit test jobs
…aram structs TranscriptParams was snake_case-only (src_language, content_type, enable_lid) while Audio/Video/DeskshareParams were camelCase-only (dataOpt, sampleRate, etc.) causing AttributeError when using srcLanguage in Python code. Add the missing direction in each struct so both forms always work: - TranscriptParams: add srcLanguage, contentType, enableLid camelCase aliases - AudioParams: add data_opt, sample_rate, content_type, frame_size snake_case aliases - VideoParams: add data_opt, content_type snake_case aliases - DeskshareParams: add data_opt, content_type snake_case aliases Update python.md docs to consistently use snake_case (data_opt, src_language) as the canonical form while noting camelCase still works.
The previous fix set sdk_opened_=false inside Client::release(), but PyClient::release() calls stopCallbacks() BEFORE client_->release(), so the guard was never reached in time. Real fix: add Client::markClosed() which sets sdk_opened_=false, then call it from PyClient::release() before stopCallbacks(). This makes the four setOnAudioData/Video/Deskshare/Transcript calls in stopCallbacks() hit the sdk_opened_ guard in updateMediaConfiguration and short-circuit cleanly. Client::release() retains its own sdk_opened_=false assignment for any path that calls it directly without going through PyClient.
EventLoop thread calls poll() while the webhook thread calls leave() → release() → sdk_ = nullptr concurrently, causing a use-after-free. Fix: add poll_mutex_ to PyClient. - poll() releases the GIL *before* acquiring the mutex so the webhook thread (which holds the GIL while waiting for the mutex) never deadlocks the EventLoop thread (which holds the mutex without the GIL) - release() holds the mutex for its entire sequence so it waits for any in-flight poll() to finish before tearing down the C SDK handle - client_.reset() at the end of release() ensures subsequent poll() calls see nullptr and return early Result: meeting end no longer crashes the process (exit 139 / SIGSEGV)
The split between AudioDataOption and VideoDataOption was artificial — the underlying C++ enum (MEDIA_DATA_OPTION) is already unified. Having two separate namespaces forced users to know which one to use for a given param struct. DataOption is now the canonical name with all audio and video stream delivery modes. AudioDataOption and VideoDataOption are kept as aliases so existing code continues to work without changes. Also fixes VIDEO_SINGLE_INDIVIDUAL_STREAM which was missing from the Python binding (only VIDEO_MIXED_SPEAKER_VIEW, the legacy name, was exposed).
Add entries for the fixes landed after the initial changelog write: - DataOption unified enum (replaces split AudioDataOption/VideoDataOption) - Bidirectional snake_case/camelCase param aliases - Spurious configure warnings on leave - SIGSEGV race condition on meeting end (Python) - CI test path fixes and C++ unit test jobs
C++ tests: add Node.js setup and run node scripts/check-deps.js before cmake to download SDK headers from GitHub releases. RTMS_BUILD_TESTS=ON mocks the SDK binary but still needs the headers for type definitions. Node.js wrapper tests: add npx tsc step after npm install to produce build/Release/index.js. The rtms.wrapper.test.ts integration test loads the real compiled module and requires this file to exist; npm install extracts the native prebuild but does not compile TypeScript.
install.js exits early when .git exists (dev mode detection), so npm install never extracts the native prebuild in CI. Add an explicit prebuild-install step to place rtms.node in build/Release/, then compile TypeScript with tsc to produce build/Release/index.js. With both files in place the wrapper integration tests can load the real built module.
install.js exits early when .git exists (dev mode detection). Add a --force flag to bypass this check so CI can run the full install flow (prebuild extraction + macOS framework unpacking) against the prebuilds downloaded from the build artifact, without changing behavior for regular npm install.
Node.js 20.x reached EOL in April 2026. Per the project's EOL policy, update the minimum supported version to 22.0.0 (current LTS) and add Node.js 24.x to the CI test matrix alongside 22.x. Update engines field in package.json, CI matrix, and all docs accordingly.
execSync('prebuild-install') fails in CI because the binary is only in
node_modules/.bin/, not in system PATH. Resolve the full path explicitly
so the install script works in both CI and end-user environments.
execSync('prebuild-install') fails in CI when called directly outside
npm install because node_modules/.bin/ isn't in PATH. Check for the
local binary first; fall back to the bare command which npm's PATH
augmentation covers during lifecycle scripts.
Also switch the macOS CI test step to use install.js --force instead
of npx prebuild-install directly, so the macOS framework archive
extraction (extractFrameworks) runs and .framework.tar.gz files are
unpacked before the tests load the native module.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
v1.1.0 adds Zoom Contact Center (ZCC) support, HTTP proxy configuration, transcript language
hints, per-participant video subscriptions, and a full Python concurrency upgrade (asyncio,
executor dispatch, EventLoop/EventLoopPool). Also includes several stability fixes — notably
suppressing spurious configure warnings on leave and a SIGSEGV race condition in the Python
poll/release path.
Related Issues
N/A
Type of Change
Affected Components
Testing Performed
run_async, executor dispatchChecklist
Additional Context
Key fixes worth calling out:
updateMediaConfigurationwas firing during callback teardown after session close — fixed by trackingsdk_opened_stateDataOption: Unified stream delivery enum replacing the artificial split betweenAudioDataOptionandVideoDataOption; both kept as backward-compat aliases