Lazy-load fast-png to fix crash in React Native / Hermes#1166
Lazy-load fast-png to fix crash in React Native / Hermes#1166
fast-png to fix crash in React Native / Hermes#1166Conversation
…ative/Hermes Co-authored-by: EzraBrooks <1959658+EzraBrooks@users.noreply.github.com>
fast-png to fix crash in React Native / Hermes
|
@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>
Done in 8f0d0ab. Added a comment above the |
|
@cn1001wang could you test if this fixes your issue? |
|
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? |
|
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 |
|
I agree, if we could make an interface that allows to make simple plugins/extensions then this can be something like that. |
|
Hi @EzraBrooks, I'm cn1001wang. I just tested this PR in a React Native environment, and I’m happy to report that the For other developers using this in React Native, just a small tip: since I've written a wrapper layer (You can see the full implementation here: RosManagers.ts). It features:
// 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 |
|
@EzraBrooks Would it be worthwhile to add specific framework examples in the Roslib examples? |
|
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. |
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. |
There was a problem hiding this comment.
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
decompressPngto dynamicallyimport("fast-png")and becomeasync. - Update
AbstractTransport.handleRosbridgePngMessageto 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-pngcode.
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.
| // 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)); |
There was a problem hiding this comment.
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.
| @@ -15,22 +14,27 @@ const textDecoder = new TextDecoder(); | |||
| * | |||
| * @param data - An object containing the PNG data. | |||
There was a problem hiding this comment.
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.
| * @param data - An object containing the PNG data. | |
| * @param data - A base64-encoded PNG string containing the compressed JSON data. |
Sounds like a great idea. |
fast-pngcreates aTextDecoder('latin1')at import-time, crashing Hermes (React Native)decompressPng.tsto use dynamicimport("fast-png")instead of static import, making function asynchandleRosbridgePngMessageinTransport.tsto handle the asyncdecompressPngpromiseOriginal prompt
fast-pngdependency creates alatin1TextDecoder at import-time, crashing runtimes that don't containlatin1support #1162💡 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.