Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 11 additions & 6 deletions packages/roslib/src/core/transport/Transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,17 @@ export abstract class AbstractTransport
* It is one technique for compressing JSON data.
*/
private handleRosbridgePngMessage(message: RosbridgePngMessage) {
const decoded = decompressPng(message.data);
if (isRosbridgeMessage(decoded)) {
this.handleRosbridgeMessage(decoded);
} else {
throw new Error("Decompressed PNG data was invalid!");
}
decompressPng(message.data)
.then((decoded) => {
if (isRosbridgeMessage(decoded)) {
this.handleRosbridgeMessage(decoded);
} else {
this.emit("error", new Error("Decompressed PNG data was invalid!"));
}
})
.catch((error: unknown) => {
this.emit("error", error);
});
}

/**
Expand Down
24 changes: 14 additions & 10 deletions packages/roslib/src/util/decompressPng.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import type { DecodedPng } from "fast-png";
import { decode } from "fast-png";

const textDecoder = new TextDecoder();

Expand All @@ -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.
*/
export default function decompressPng(data: string): unknown {
export default async function decompressPng(data: string): Promise<unknown> {
// 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));
Comment on lines +18 to 26
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.

const decoded = tryDecodeBuffer(buffer);

let decoded: DecodedPng;
try {
return JSON.parse(textDecoder.decode(decoded.data));
decoded = decode(buffer);
} catch (error) {
throw new Error("Error parsing PNG JSON contents", { cause: error });
throw new Error("Error decoding PNG buffer", { cause: error });
}
}

function tryDecodeBuffer(buffer: Uint8Array): DecodedPng {
try {
return decode(buffer);
return JSON.parse(textDecoder.decode(decoded.data));
} catch (error) {
throw new Error("Error decoding PNG buffer", { cause: error });
throw new Error("Error parsing PNG JSON contents", { cause: error });
}
}
Loading