Skip to content

MatrixRTCSession: .finally() without argument crashes on React Native / Hermes — TypeError: undefined is not a function #5308

@JeanLuX

Description

@JeanLuX

Description

Using matrix-js-sdk v41.4.0 in a React Native application with the Hermes JS engine, every call to MatrixRTCSession.ensureRecalculateSessionMembers() throws an unhandled TypeError: undefined is not a function at startup.

The error is silent in normal Metro output (it appears only as a sequence of opaque unhandled promise rejection IDs) and leaves the entire MatrixRTCSession layer in a permanently broken state,every room's initialMembershipCalculated promise is rejected, so RTC session membership is never computed.


Steps to reproduce

  1. Create a React Native app (Expo SDK 55 / bare RN 0.83.6) that uses matrix-js-sdk 41.4.0.
  2. Authenticate and start a Matrix client with at least one joined room.
  3. Call client.matrixRTC.start() (or rely on automatic start during sync).
  4. Observe Metro / logcat output.

Expected: RTC sessions initialise silently.
Actual: One TypeError: undefined is not a function per room appears in the Metro log, all originating from promise/setimmediate/finally.js (React Native's internal Promise polyfill) via InternalBytecode.js.


Minimal reproduction snippet

// Minimal probe,run this anywhere after React Native initialises:
Promise.resolve()
  .finally()         // ← no argument
  .then(() => console.log("OK"))
  .catch((e) => console.error("CRASH:", e.message)); // prints "CRASH: undefined is not a function"

Root cause

MatrixRTCSession.ensureRecalculateSessionMembers() in
src/matrixrtc/MatrixRTCSession.ts contains:

this.recalculateSessionMembersPromise =
  this.recalculateSessionMembersPromise
    .finally()   // ← called with no argument (undefined)
    .then(() => this.recalculateSessionMembers());

In browsers and Node.js, Promise.prototype.finally(undefined) is a spec-compliant no-op that passes the settled value through. However, the Promise polyfill shipped with React Native (promise/setimmediate/finally.js) performs a strict typeof onFinally !== 'function' check and throws rather than treating undefined as a pass-through.

Spec reference,MDN: Promise.prototype.finally:

If onFinally is not a function, it is internally replaced with a function that simply returns the received value. This means .finally() (no argument) is equivalent to .then(x => x, x => { throw x; }) and must not throw.

React Native's polyfill diverges from this requirement.


Affected versions

Component Version
matrix-js-sdk 41.4.0 (regression introduced with MatrixRTC refactor)
React Native 0.83.6
Hermes bundled with RN 0.83.6
Expo SDK 55
promise npm package (RN built-in polyfill) ships with RN, promise/setimmediate/finally.js
Node.js (dev host) 24.15.0 LTS
iOS / Android both affected (Hermes is default on both)

Impact

  • All React Native clients using matrix-js-sdk ≥ v41 are affected whenever at least one joined room exists.
  • MatrixRTCSession.initialMembershipCalculated is rejected for every room, silently disabling membership tracking for the entire RTC layer.
  • No console error is produced by default,the failure is visible only via Metro's opaque rejection IDs.

Suggested fix

Replace .finally().then(fn) with .then(onFulfilled, onRejected),semantically identical, compatible with all runtimes:

- this.recalculateSessionMembersPromise = this.recalculateSessionMembersPromise
-   .finally()
-   .then(() => this.recalculateSessionMembers());
+ this.recalculateSessionMembersPromise = this.recalculateSessionMembersPromise.then(
+   () => this.recalculateSessionMembers(),
+   () => this.recalculateSessionMembers(),
+ );

A PR with this fix is available: #5307


Workaround (for app developers)

Until an upstream release is published, patch node_modules/matrix-js-sdk with
patch-package:

# patches/matrix-js-sdk+41.4.0.patch
-    this.recalculateSessionMembersPromise = this.recalculateSessionMembersPromise.finally().then(() => this.recalculateSessionMembers());
+    this.recalculateSessionMembersPromise = this.recalculateSessionMembersPromise.then(
+      () => this.recalculateSessionMembers(),
+      () => this.recalculateSessionMembers(),
+    );

Then add to package.json:

"scripts": {
  "postinstall": "patch-package"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions