Skip to content

Lazy-load fast-png to fix crash in React Native / Hermes#1166

Open
Copilot wants to merge 3 commits intodevelopfrom
copilot/fix-latin1-textdecoder-crash
Open

Lazy-load fast-png to fix crash in React Native / Hermes#1166
Copilot wants to merge 3 commits intodevelopfrom
copilot/fix-latin1-textdecoder-crash

Conversation

Copy link
Contributor

Copilot AI commented Mar 2, 2026

  • Understand the issue: fast-png creates a TextDecoder('latin1') at import-time, crashing Hermes (React Native)
  • Change decompressPng.ts to use dynamic import("fast-png") instead of static import, making function async
  • Update handleRosbridgePngMessage in Transport.ts to handle the async decompressPng promise
  • Add inline comment explaining why the lazy import is necessary, with a link to the upstream code
  • Run tests to verify changes
  • Code review and security scan
Original prompt

This section details on the original issue you should resolve

<issue_title>fast-png dependency creates a latin1 TextDecoder at import-time, crashing runtimes that don't contain latin1 support</issue_title>
<issue_description>## Environment

  • roslib version: 2.x (latest)
  • Platform: React Native (Expo Bare)
  • JS engine: Hermes
  • OS: Android (also reproducible on iOS with Hermes)
  • Transport: rosbridge WebSocket

Description

After upgrading from roslib 1.4.1 to 2.x, the app crashes at startup in a React Native environment using Hermes. The error appears to originate from dependencies (likely BSON / Buffer usage) that rely on encodings unsupported by Hermes.

Error Logs

WARN  BSON: For React Native please polyfill crypto.getRandomValues
ERROR RangeError: Unknown encoding: latin1 (normalized: latin1), js engine: hermes

Steps to Reproduce

  1. Create a React Native project (Expo Bare or CLI).
  2. Enable Hermes (default in recent RN versions).
  3. Install roslib 2.x:
npm install roslib
  1. Import and create a Ros instance:
import { Ros } from 'roslib'

const ros = new Ros({
  url: 'ws://localhost:9090'
})
  1. Run on Android or iOS.

Expected Behavior

roslib should initialize normally in React Native, or documentation should clarify that RN is not supported.

Actual Behavior

Application crashes with:

RangeError: Unknown encoding: latin1

Questions

  1. Is React Native (Hermes) officially supported in roslib v2?
  2. Are there recommended polyfills or configuration steps to make v2 work in RN?
  3. If RN is not supported, would it be possible to document this limitation?
  4. Is there any plan to provide a RN-compatible build (without Node-specific encoding dependencies)?

Thank you!
</issue_description>

<agent_instructions>change handleRosbridgePngMessage to lazily import fast-png to avoid it being imported when users do not require PNG support</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@MatthijsBurgh I don't think we support any frameworks like react. They work or not. We are open to fixes that extend compatibility.

@EzraBrooks do you have any tips to get it working?</comment_new>
<comment_new>@EzraBrooks
I think we certainly should support React, etc. since it's how most people write JS apps.

This seems like a bug internal to bson, unfortunately. Are you actually using BSON on your rosbridge, or is roslibjs erroneously attempting to do BSON things where it's not needed?</comment_new>
<comment_new>@EzraBrooks
Actually, this line in fast-png appears to be the culprit:

https://github.com/image-js/fast-png/blob/77a4479d68d84246793f58f7bbf2a2ea3a80c0f5/src/helpers/text.ts#L11

A globally-scoped TextDecoder that is constructed at import time.

I think perhaps roslibjs can lazy-load PNG support.</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…ative/Hermes

Co-authored-by: EzraBrooks <1959658+EzraBrooks@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix fast-png dependency causing crash in React Native with Hermes Lazy-load fast-png to fix crash in React Native / Hermes Mar 2, 2026
@EzraBrooks
Copy link
Contributor

@copilot please document in the comments why this change was made so someone doesn't attempt to optimize it out later.

Co-authored-by: EzraBrooks <1959658+EzraBrooks@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 2, 2026

@copilot please document in the comments why this change was made so someone doesn't attempt to optimize it out later.

Done in 8f0d0ab. Added a comment above the await import("fast-png") line explaining that the lazy import is intentional to avoid crashing Hermes (React Native), which doesn't support the 'latin1' encoding that fast-png uses at module load time, with a link to the upstream code.

@EzraBrooks
Copy link
Contributor

@cn1001wang could you test if this fixes your issue?

@ikwilnaarhuisman
Copy link
Contributor

This hopefully fixes the load in error!

But it won't fix the fact that you then won't be able to handle PNG's in react native and possibly other frameworks. Is there a nice polyfill or something to fix this?

@EzraBrooks
Copy link
Contributor

I'm kinda of the opinion that the PNG support is a weird vestigial limb we eventually need to get rid of - I wouldn't advise anyone actually use it in a greenfield application

@ikwilnaarhuisman
Copy link
Contributor

I agree, if we could make an interface that allows to make simple plugins/extensions then this can be something like that.

@cn1001wang
Copy link

Hi @EzraBrooks,

I'm cn1001wang. I just tested this PR in a React Native environment, and I’m happy to report that the TextDecoder('latin1') error (triggered by the fast-png dependency) is no longer occurring. Everything is working smoothly now!

For other developers using this in React Native, just a small tip: since roslib depends on uuid, you need to import react-native-get-random-values at the very top of your entry file to provide the necessary polyfill.


I've written a wrapper layer (You can see the full implementation here: RosManagers.ts). It features:

  • Promise-based Services & Params: Replaced nested callbacks with async/await support and built-in timeouts.
  • Topic Reference Counting: Multiple callbacks can subscribe to the same topic name without creating redundant ROS subscriptions.
  • Auto Re-subscription: Automatically restores all active subscriptions when the ROS connection is re-established.
// A snippet of my Manager implementation for reference
// It handles Topic reference counting and Service-to-Promise conversion
export class TopicManager {
  private topics: Map<string, ManagedTopic> = new Map();
  // ... (Managed subscription logic)
  subscribe(name: string, messageType: string, callback: Callback) {
    if (this.topics.has(name)) {
      this.topics.get(name)!.callbacks.add(callback);
      return;
    }
    // New subscription logic with reference tracking...
  }
}

Thank you so much for the fix! This greatly improves the compatibility of roslibjs for the mobile developer community. 🎉

@ikwilnaarhuisman
Copy link
Contributor

@EzraBrooks Would it be worthwhile to add specific framework examples in the Roslib examples?

@cn1001wang
Copy link

Tested on React Native: standard topics work fine, but compression: 'png' still crashes with RangeError: Unknown encoding: latin1.

As noted in the Expo documentation and Hermes issue #1403, the native TextDecoder in RN/Hermes does not support latin1. To fix this for mobile, users must manually provide a polyfill that includes latin1 support.

@EzraBrooks EzraBrooks marked this pull request as ready for review March 12, 2026 14:34
Copilot AI review requested due to automatic review settings March 12, 2026 14:34
@EzraBrooks
Copy link
Contributor

@EzraBrooks Would it be worthwhile to add specific framework examples in the Roslib examples?

Part of my thought process on splitting the examples out into their own package was to be able to have vanilla JS examples, React examples, and others as separate packages. Eventually I'd like us to provide first-party bindings for React as another package in this monorepo since it's the most popular JS framework.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves React Native / Hermes compatibility by preventing fast-png from being imported at module load time (where it triggers a Hermes crash due to TextDecoder('latin1')), and updates message handling to accommodate async PNG decompression.

Changes:

  • Switch decompressPng to dynamically import("fast-png") and become async.
  • Update AbstractTransport.handleRosbridgePngMessage to handle the async decompression result and emit errors on failure/invalid data.
  • Add an inline comment documenting the Hermes crash cause and linking to the upstream fast-png code.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/roslib/src/util/decompressPng.ts Lazy-loads fast-png, updates error boundaries/messages, and documents Hermes import-time crash reason.
packages/roslib/src/core/transport/Transport.ts Handles PNG messages asynchronously and emits "error" events for decompression failures/invalid decoded messages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +18 to 26
// fast-png is imported dynamically (lazily) rather than statically to avoid
// a crash in environments such as React Native / Hermes. fast-png constructs
// a `new TextDecoder('latin1')` at module load time, and Hermes does not
// support the 'latin1' encoding, causing an immediate RangeError on import.
// By deferring the import until a PNG message is actually received, users
// who do not use PNG-compressed rosbridge messages are unaffected.
// See: https://github.com/image-js/fast-png/blob/77a4479d68d84246793f58f7bbf2a2ea3a80c0f5/src/helpers/text.ts#L11
const { decode } = await import("fast-png");
const buffer = Uint8Array.from(atob(data), (char) => char.charCodeAt(0));
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await import("fast-png") can fail (e.g., Hermes rejecting module evaluation with the original latin1 RangeError) and currently that error bubbles up without additional context. Consider wrapping the dynamic import in a try/catch to rethrow a more actionable error (with cause), and optionally cache the imported decode function in a module-scoped variable so repeated PNG messages don't pay an import()/await overhead each time.

Copilot uses AI. Check for mistakes.
@@ -15,22 +14,27 @@ const textDecoder = new TextDecoder();
*
* @param data - An object containing the PNG data.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc for data says "An object containing the PNG data", but data is a base64-encoded PNG string. Updating the parameter description would avoid confusion for callers and keep the docs aligned with the actual API.

Suggested change
* @param data - An object containing the PNG data.
* @param data - A base64-encoded PNG string containing the compressed JSON data.

Copilot uses AI. Check for mistakes.
@ikwilnaarhuisman
Copy link
Contributor

@EzraBrooks Would it be worthwhile to add specific framework examples in the Roslib examples?

Part of my thought process on splitting the examples out into their own package was to be able to have vanilla JS examples, React examples, and others as separate packages. Eventually I'd like us to provide first-party bindings for React as another package in this monorepo since it's the most popular JS framework.

Sounds like a great idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fast-png dependency creates a latin1 TextDecoder at import-time, crashing runtimes that don't contain latin1 support

5 participants