@umituz/react-native-ai-fal-provider 3.2.13 → 3.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-fal-provider",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.15",
|
|
4
4
|
"description": "FAL AI provider for React Native - implements IAIProvider interface for unified AI generation",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -42,6 +42,23 @@ export async function uploadToFalStorage(base64: string): Promise<string> {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Upload a local file (file:// or content:// URI) to FAL storage
|
|
47
|
+
* Directly fetches the file as a blob — no base64 intermediate step
|
|
48
|
+
*/
|
|
49
|
+
export async function uploadLocalFileToFalStorage(fileUri: string): Promise<string> {
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(fileUri);
|
|
52
|
+
const blob = await response.blob();
|
|
53
|
+
const url = await fal.storage.upload(blob);
|
|
54
|
+
return url;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Failed to upload local file to FAL storage: ${getErrorMessage(error)}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
/**
|
|
46
63
|
* Upload multiple images to FAL storage in parallel
|
|
47
64
|
* Uses Promise.allSettled to handle partial failures gracefully
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Input Preprocessor Utility
|
|
3
|
-
* Detects and uploads base64 images to FAL storage before API calls
|
|
3
|
+
* Detects and uploads base64/local file images to FAL storage before API calls
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { uploadToFalStorage } from "./fal-storage.util";
|
|
6
|
+
import { uploadToFalStorage, uploadLocalFileToFalStorage } from "./fal-storage.util";
|
|
7
7
|
import { getErrorMessage } from './helpers/error-helpers.util';
|
|
8
8
|
import { IMAGE_URL_FIELDS } from './constants/image-fields.constants';
|
|
9
9
|
import { isImageDataUri as isBase64DataUri } from './validators/data-uri-validator.util';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
|
|
12
|
+
* Check if a value is a local file URI (file:// or content://)
|
|
13
|
+
*/
|
|
14
|
+
function isLocalFileUri(value: unknown): value is string {
|
|
15
|
+
return typeof value === "string" && (
|
|
16
|
+
value.startsWith("file://") || value.startsWith("content://")
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Preprocess input by uploading base64/local file images to FAL storage.
|
|
22
|
+
* Also strips sync_mode to prevent base64 data URI responses.
|
|
23
|
+
* Returns input with HTTPS URLs instead of base64/local URIs.
|
|
14
24
|
*/
|
|
15
25
|
export async function preprocessInput(
|
|
16
26
|
input: Record<string, unknown>,
|
|
@@ -18,9 +28,24 @@ export async function preprocessInput(
|
|
|
18
28
|
const result = { ...input };
|
|
19
29
|
const uploadPromises: Promise<unknown>[] = [];
|
|
20
30
|
|
|
31
|
+
// SAFETY: Strip sync_mode to prevent base64 data URI responses
|
|
32
|
+
// FAL returns base64 when sync_mode:true — we always want CDN URLs
|
|
33
|
+
if ("sync_mode" in result) {
|
|
34
|
+
delete result.sync_mode;
|
|
35
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
36
|
+
console.warn(
|
|
37
|
+
"[preprocessInput] Stripped sync_mode from input. " +
|
|
38
|
+
"sync_mode:true returns base64 data URIs which break Firestore persistence. " +
|
|
39
|
+
"Use falProvider.subscribe() for CDN URLs."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
// Handle individual image URL keys
|
|
22
45
|
for (const key of IMAGE_URL_FIELDS) {
|
|
23
46
|
const value = result[key];
|
|
47
|
+
|
|
48
|
+
// Upload base64 data URIs to FAL storage
|
|
24
49
|
if (isBase64DataUri(value)) {
|
|
25
50
|
const uploadPromise = uploadToFalStorage(value)
|
|
26
51
|
.then((url) => {
|
|
@@ -35,8 +60,24 @@ export async function preprocessInput(
|
|
|
35
60
|
|
|
36
61
|
uploadPromises.push(uploadPromise);
|
|
37
62
|
}
|
|
63
|
+
// Upload local file URIs to FAL storage (file://, content://)
|
|
64
|
+
else if (isLocalFileUri(value)) {
|
|
65
|
+
const uploadPromise = uploadLocalFileToFalStorage(value)
|
|
66
|
+
.then((url) => {
|
|
67
|
+
result[key] = url;
|
|
68
|
+
return url;
|
|
69
|
+
})
|
|
70
|
+
.catch((error) => {
|
|
71
|
+
const errorMessage = `Failed to upload local file ${key}: ${getErrorMessage(error)}`;
|
|
72
|
+
console.error(`[preprocessInput] ${errorMessage}`);
|
|
73
|
+
throw new Error(errorMessage);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
uploadPromises.push(uploadPromise);
|
|
77
|
+
}
|
|
38
78
|
}
|
|
39
79
|
|
|
80
|
+
|
|
40
81
|
// Handle image_urls array (for multi-person generation)
|
|
41
82
|
if (Array.isArray(result.image_urls) && result.image_urls.length > 0) {
|
|
42
83
|
const imageUrls = result.image_urls as unknown[];
|
|
@@ -22,7 +22,6 @@ function hasSuspiciousContent(value: string): boolean {
|
|
|
22
22
|
/<object/i, // object tags
|
|
23
23
|
/data:(?!image\/)/i, // data URLs that aren't images
|
|
24
24
|
/vbscript:/i, // vbscript protocol
|
|
25
|
-
/file:/i, // file protocol
|
|
26
25
|
];
|
|
27
26
|
|
|
28
27
|
return suspiciousPatterns.some(pattern => pattern.test(value));
|
|
@@ -52,6 +51,11 @@ function isValidAndSafeUrl(value: string): boolean {
|
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
// Allow local file URIs (file://, content://) — preprocessInput uploads them to FAL storage
|
|
55
|
+
if (value.startsWith('file://') || value.startsWith('content://')) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
55
59
|
// Allow base64 image data URIs only
|
|
56
60
|
if (isImageDataUri(value)) {
|
|
57
61
|
// Check for suspicious content in data URI
|
|
@@ -95,6 +99,14 @@ export function validateInput(
|
|
|
95
99
|
errors.push({ field: "input", message: "Input must be a non-empty object" });
|
|
96
100
|
}
|
|
97
101
|
|
|
102
|
+
// BLOCK sync_mode:true — it causes FAL to return base64 data URIs instead of CDN URLs
|
|
103
|
+
if (input.sync_mode === true) {
|
|
104
|
+
errors.push({
|
|
105
|
+
field: "sync_mode",
|
|
106
|
+
message: "sync_mode:true is forbidden. It returns base64 data URIs instead of HTTPS CDN URLs, which breaks Firestore persistence (2048 char limit). Use falProvider.subscribe() for CDN URLs.",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
98
110
|
// Validate and check prompt for malicious content
|
|
99
111
|
if (input.prompt !== undefined) {
|
|
100
112
|
if (!isValidPrompt(input.prompt)) {
|