diff --git a/packages/jsActions/nanoflow-actions-native/CHANGELOG.md b/packages/jsActions/nanoflow-actions-native/CHANGELOG.md index 5bf32878f..b19e0f991 100644 --- a/packages/jsActions/nanoflow-actions-native/CHANGELOG.md +++ b/packages/jsActions/nanoflow-actions-native/CHANGELOG.md @@ -6,10 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We've fixed a synchronization issue with Base64 images generated by the Signature widget. + ## [5.2.0] Nanoflow Commons - 2026-1-23 - We've migrated from using @react-native-community/geolocation to react-native-permissions for handling location permissions. -- ## [5.1.6] Nanoflow Commons - 2025-12-05 diff --git a/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts b/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts index f7243f73d..21803728f 100644 --- a/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts +++ b/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts @@ -6,6 +6,7 @@ // - the code between BEGIN EXTRA CODE and END EXTRA CODE // Other code you write will be lost the next time you deploy the project. import { Base64 } from "js-base64"; +import RNBlobUtil from "react-native-blob-util"; // BEGIN EXTRA CODE // END EXTRA CODE @@ -16,7 +17,7 @@ import { Base64 } from "js-base64"; * @param {MxObject} image * @returns {Promise.} */ -export function Base64DecodeToImage(base64: string, image: mendix.lib.MxObject): Promise { +export async function Base64DecodeToImage(base64: string, image: mendix.lib.MxObject): Promise { // BEGIN USER CODE if (!base64) { @@ -26,11 +27,52 @@ export function Base64DecodeToImage(base64: string, image: mendix.lib.MxObject): throw new Error("image should not be null"); } - const blob = new Blob([Base64.toUint8Array(base64)], { type: "image/png" }); + // Native platform + if (navigator && navigator.product === "ReactNative") { + try { + // Remove data URI prefix if present (e.g., "data:image/png;base64,") + let cleanBase64 = base64; + if (base64.includes(",")) { + cleanBase64 = base64.split(",")[1]; + } + + // Remove any whitespace/newlines + cleanBase64 = cleanBase64.replace(/\s/g, ""); + + // Validate base64 format + if (!/^[A-Za-z0-9+/]*={0,2}$/.test(cleanBase64)) { + throw new Error("Invalid base64 format"); + } + + // Create a temporary file path + const tempPath = `${RNBlobUtil.fs.dirs.CacheDir}/temp_image_${Date.now()}.png`; + + // Write Base64 data to a temporary file + await RNBlobUtil.fs.writeFile(tempPath, cleanBase64, "base64"); + // Fetch the file as a blob + const res = await fetch(`file://${tempPath}`); + const blob = await res.blob(); + + return new Promise((resolve, reject) => { + mx.data.saveDocument(image.getGuid(), "camera image", {}, blob, () => { + RNBlobUtil.fs.unlink(tempPath).catch(() => { }); + resolve(true); + }, (error) => { + RNBlobUtil.fs.unlink(tempPath).catch(() => { }); + reject(error); + }); + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to decode base64 to image: ${errorMessage}`); + } + } + + // Other platforms + const blob = new Blob([Base64.toUint8Array(base64)], { type: "image/png" }); return new Promise((resolve, reject) => { mx.data.saveDocument(image.getGuid(), "camera image", {}, blob, () => resolve(true), reject); }); - // END USER CODE }