@umituz/react-native-ai-fal-provider 3.2.14 → 3.2.16

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.14",
3
+ "version": "3.2.16",
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",
@@ -146,6 +146,9 @@ export async function handleFalSubscription<T = unknown>(
146
146
  const statusWithPosition = `${jobStatus.status}:${jobStatus.queuePosition ?? ""}`;
147
147
  if (statusWithPosition !== lastStatus) {
148
148
  lastStatus = statusWithPosition;
149
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
150
+ console.log(`[fal-provider] Job Status Update: ${jobStatus.status}${jobStatus.queuePosition ? ` (Position: ${jobStatus.queuePosition})` : ""}`);
151
+ }
149
152
  if (options?.onProgress) {
150
153
  if (jobStatus.status === "IN_QUEUE" || jobStatus.status === "IN_PROGRESS") {
151
154
  options.onProgress({ progress: -1, status: jobStatus.status });
@@ -182,9 +185,15 @@ export async function handleFalSubscription<T = unknown>(
182
185
  const rawResult = await Promise.race(promises);
183
186
  const { data, requestId } = unwrapFalResult<T>(rawResult);
184
187
 
188
+ // Validate response for base64 data URIs
189
+ // Since we use subscribe, we should always get CDN URLs, not base64 data URIs
185
190
  validateNoBase64InResponse(data);
186
191
  validateNSFWContent(data as Record<string, unknown>);
187
192
 
193
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
194
+ console.log(`[fal-provider] Subscription completed successfully. Request ID: ${requestId}`);
195
+ }
196
+
188
197
  options?.onResult?.(data);
189
198
  return { result: data, requestId };
190
199
  } catch (error) {
@@ -15,6 +15,10 @@ import { getErrorMessage } from './helpers/error-helpers.util';
15
15
  * Uses design system's filesystem utilities for React Native compatibility
16
16
  */
17
17
  export async function uploadToFalStorage(base64: string): Promise<string> {
18
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
19
+ console.log(`[fal-storage] Uploading base64 image to FAL (first 50 chars): ${base64.substring(0, 50)}...`);
20
+ }
21
+
18
22
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
19
23
  const tempUri = (await base64ToTempFile(base64));
20
24
 
@@ -26,6 +30,11 @@ export async function uploadToFalStorage(base64: string): Promise<string> {
26
30
  const response = await fetch(tempUri);
27
31
  const blob = await response.blob();
28
32
  const url = await fal.storage.upload(blob);
33
+
34
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
35
+ console.log(`[fal-storage] Successfully uploaded base64 data to FAL. URL: ${url}`);
36
+ }
37
+
29
38
  return url;
30
39
  } finally {
31
40
  try {
@@ -42,6 +51,32 @@ export async function uploadToFalStorage(base64: string): Promise<string> {
42
51
  }
43
52
  }
44
53
 
54
+ /**
55
+ * Upload a local file (file:// or content:// URI) to FAL storage
56
+ * Directly fetches the file as a blob — no base64 intermediate step
57
+ */
58
+ export async function uploadLocalFileToFalStorage(fileUri: string): Promise<string> {
59
+ try {
60
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
61
+ console.log(`[fal-storage] Starting local file upload to FAL: ${fileUri}`);
62
+ }
63
+
64
+ const response = await fetch(fileUri);
65
+ const blob = await response.blob();
66
+ const url = await fal.storage.upload(blob);
67
+
68
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
69
+ console.log(`[fal-storage] Successfully uploaded local file to FAL. URL: ${url}`);
70
+ }
71
+
72
+ return url;
73
+ } catch (error) {
74
+ throw new Error(
75
+ `Failed to upload local file to FAL storage: ${getErrorMessage(error)}`
76
+ );
77
+ }
78
+ }
79
+
45
80
  /**
46
81
  * Upload multiple images to FAL storage in parallel
47
82
  * Uses Promise.allSettled to handle partial failures gracefully
@@ -1,26 +1,57 @@
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
- * Preprocess input by uploading base64 images to FAL storage
13
- * Returns input with URLs instead of base64 data URIs
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>,
17
27
  ): Promise<Record<string, unknown>> {
28
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
29
+ console.log("[preprocessInput] Starting input preprocessing...", {
30
+ keys: Object.keys(input)
31
+ });
32
+ }
33
+
18
34
  const result = { ...input };
19
35
  const uploadPromises: Promise<unknown>[] = [];
20
36
 
37
+ // SAFETY: Strip sync_mode to prevent base64 data URI responses
38
+ // FAL returns base64 when sync_mode:true — we always want CDN URLs
39
+ if ("sync_mode" in result) {
40
+ delete result.sync_mode;
41
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
42
+ console.warn(
43
+ "[preprocessInput] Stripped sync_mode from input. " +
44
+ "sync_mode:true returns base64 data URIs which break Firestore persistence. " +
45
+ "Use falProvider.subscribe() for CDN URLs."
46
+ );
47
+ }
48
+ }
49
+
21
50
  // Handle individual image URL keys
22
51
  for (const key of IMAGE_URL_FIELDS) {
23
52
  const value = result[key];
53
+
54
+ // Upload base64 data URIs to FAL storage
24
55
  if (isBase64DataUri(value)) {
25
56
  const uploadPromise = uploadToFalStorage(value)
26
57
  .then((url) => {
@@ -35,8 +66,24 @@ export async function preprocessInput(
35
66
 
36
67
  uploadPromises.push(uploadPromise);
37
68
  }
69
+ // Upload local file URIs to FAL storage (file://, content://)
70
+ else if (isLocalFileUri(value)) {
71
+ const uploadPromise = uploadLocalFileToFalStorage(value)
72
+ .then((url) => {
73
+ result[key] = url;
74
+ return url;
75
+ })
76
+ .catch((error) => {
77
+ const errorMessage = `Failed to upload local file ${key}: ${getErrorMessage(error)}`;
78
+ console.error(`[preprocessInput] ${errorMessage}`);
79
+ throw new Error(errorMessage);
80
+ });
81
+
82
+ uploadPromises.push(uploadPromise);
83
+ }
38
84
  }
39
85
 
86
+
40
87
  // Handle image_urls array (for multi-person generation)
41
88
  if (Array.isArray(result.image_urls) && result.image_urls.length > 0) {
42
89
  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)) {