From 3d6358afdb355e464134701ee83648ccfe7060f0 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Tue, 9 Dec 2025 13:12:49 +0000 Subject: [PATCH 01/27] feat(cloud_functions): httpsCallable.stream support --- packages/functions/lib/namespaced.ts | 67 +++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/functions/lib/namespaced.ts b/packages/functions/lib/namespaced.ts index 857597108e..f64e92bba6 100644 --- a/packages/functions/lib/namespaced.ts +++ b/packages/functions/lib/namespaced.ts @@ -75,6 +75,8 @@ const statics = { HttpsErrorCode, }; +const nativeEvents = ['functions_streaming_event']; + // Export the complete FirebaseFunctionsTypes namespace // eslint-disable-next-line @typescript-eslint/no-namespace export namespace FirebaseFunctionsTypes { @@ -155,12 +157,25 @@ class FirebaseFunctionsModule extends FirebaseModule { _customUrlOrRegion: string; private _useFunctionsEmulatorHost: string | null; private _useFunctionsEmulatorPort: number; + private _id_functions_streaming_event: number; + // TODO: config is app package (FirebaseModule) object to be typed in the future constructor(app: FirebaseApp, config: any, customUrlOrRegion: string | null) { super(app, config, customUrlOrRegion); this._customUrlOrRegion = customUrlOrRegion || 'us-central1'; this._useFunctionsEmulatorHost = null; this._useFunctionsEmulatorPort = -1; + this._id_functions_streaming_event = 0; + + // @ts-ignore - emitter and eventNameForApp exist on FirebaseModule + this.emitter.addListener(this.eventNameForApp('functions_streaming_event'), (event: { listenerId: any; }) => { + // @ts-ignore + this.emitter.emit( + // @ts-ignore + this.eventNameForApp(`functions_streaming_event:${event.listenerId}`), + event, + ); + }); } httpsCallable(name: string, options: HttpsCallableOptions = {}) { @@ -172,7 +187,7 @@ class FirebaseFunctionsModule extends FirebaseModule { } } - return (data?: any) => { + const callableFunction: any = (data?: any) => { const nativePromise = this.native.httpsCallable( this._useFunctionsEmulatorHost, this._useFunctionsEmulatorPort, @@ -194,6 +209,56 @@ class FirebaseFunctionsModule extends FirebaseModule { ); }); }; + + // Add a streaming helper (callback-based) + // Usage: const stop = functions().httpsCallable('fn').stream(data, (evt) => {...}, options) + callableFunction.stream = (data?: any, onEvent?: (event: any) => void, streamOptions: HttpsCallableOptions = {}) => { + if (streamOptions.timeout) { + if (isNumber(streamOptions.timeout)) { + streamOptions.timeout = streamOptions.timeout / 1000; + } else { + throw new Error('HttpsCallableOptions.timeout expected a Number in milliseconds'); + } + } + + const listenerId = this._id_functions_streaming_event++; + // @ts-ignore + const eventName = this.eventNameForApp(`functions_streaming_event:${listenerId}`); + + // @ts-ignore + const subscription = this.emitter.addListener(eventName, (event: any) => { + const body = event.body; + if (onEvent) { + onEvent(body); + } + if (body && (body.done || body.error)) { + subscription.remove(); + if (this.native.removeFunctionsStreaming) { + this.native.removeFunctionsStreaming(listenerId); + } + } + }); + + // Start native streaming on both platforms. + // Note: appName and customUrlOrRegion are automatically prepended by the native module wrapper + this.native.httpsCallableStream( + this._useFunctionsEmulatorHost || null, + this._useFunctionsEmulatorPort || -1, + name, + { data }, + streamOptions, + listenerId, + ); + + return () => { + subscription.remove(); + if (this.native.removeFunctionsStreaming) { + this.native.removeFunctionsStreaming(listenerId); + } + }; + }; + + return callableFunction; } httpsCallableFromUrl(url: string, options: HttpsCallableOptions = {}) { From f20f329c967746328f2b620db12cf4e3555638cf Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Wed, 10 Dec 2025 12:46:38 +0000 Subject: [PATCH 02/27] feat: android impl --- .../FirebaseFunctionsStreamHandler.java | 61 ++++++++++++++++++ .../UniversalFirebaseFunctionsModule.java | 63 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 packages/functions/android/src/main/java/io/invertase/firebase/functions/FirebaseFunctionsStreamHandler.java diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/FirebaseFunctionsStreamHandler.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/FirebaseFunctionsStreamHandler.java new file mode 100644 index 0000000000..a5ee5d5027 --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/FirebaseFunctionsStreamHandler.java @@ -0,0 +1,61 @@ +package io.invertase.firebase.functions; + +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import io.invertase.firebase.interfaces.NativeEvent; + +public class FirebaseFunctionsStreamHandler implements NativeEvent { + static final String FUNCTIONS_STREAMING_EVENT = "functions_streaming_event"; + private static final String KEY_ID = "listenerId"; + private static final String KEY_BODY = "body"; + private static final String KEY_APP_NAME = "appName"; + private static final String KEY_EVENT_NAME = "eventName"; + private String eventName; + private WritableMap eventBody; + private String appName; + private int listenerId; + + FirebaseFunctionsStreamHandler( + String eventName, WritableMap eventBody, String appName, int listenerId) { + this.eventName = eventName; + this.eventBody = eventBody; + this.appName = appName; + this.listenerId = listenerId; + } + + @Override + public String getEventName() { + return eventName; + } + + @Override + public WritableMap getEventBody() { + WritableMap event = Arguments.createMap(); + event.putInt(KEY_ID, listenerId); + event.putMap(KEY_BODY, eventBody); + event.putString(KEY_APP_NAME, appName); + event.putString(KEY_EVENT_NAME, eventName); + return event; + } + + @Override + public String getFirebaseAppName() { + return appName; + } +} diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java index af692704b8..b68663cfa6 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java @@ -18,15 +18,27 @@ */ import android.content.Context; +import android.util.SparseArray; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Tasks; import com.google.firebase.FirebaseApp; import com.google.firebase.functions.FirebaseFunctions; import com.google.firebase.functions.HttpsCallableReference; +import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter; import io.invertase.firebase.common.UniversalFirebaseModule; import java.net.URL; import java.util.concurrent.TimeUnit; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.BufferedSource; @SuppressWarnings("WeakerAccess") public class UniversalFirebaseFunctionsModule extends UniversalFirebaseModule { @@ -34,6 +46,7 @@ public class UniversalFirebaseFunctionsModule extends UniversalFirebaseModule { public static final String CODE_KEY = "code"; public static final String MSG_KEY = "message"; public static final String DETAILS_KEY = "details"; + private static SparseArray functionsStreamingListeners = new SparseArray<>(); UniversalFirebaseFunctionsModule(Context context, String serviceName) { super(context, serviceName); @@ -95,4 +108,54 @@ Task httpsCallableFromUrl( return Tasks.await(httpReference.call(data)).getData(); }); } + + Task httpsCallableStream( + String appName, + String region, + String host, + Integer port, + String name, + Object data, + ReadableMap options, + int listenerId) { + return Tasks.call( + getExecutor(), + () -> { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseFunctions functionsInstance = FirebaseFunctions.getInstance(firebaseApp, region); + HttpsCallableReference httpReference = functionsInstance.getHttpsCallable.(name, data, options); + httpReference.stream(data, options, (event) -> { + if (event.get("done")) { + removeFunctionsStreamingListener(appName, listenerId); + } else { + emitEvent(appName, listenerId, event); + } + }); + }); + } + + private void addFunctionsStreamingListener(String appName, int listenerId) { + functionsStreamingListeners.put(listenerId, null); + } + + private void removeFunctionsStreamingListener(String appName, int listenerId) { + functionsStreamingListeners.remove(listenerId); + } + + private void emitError(String appName, int listenerId, String message) { + WritableMap body = Arguments.createMap(); + body.putString("error", message != null ? message : "unknown_error"); + emitEvent(appName, listenerId, body); + } + + private void emitEvent(String appName, int listenerId, WritableMap body) { + ReactNativeFirebaseEventEmitter.emitEvent( + appName, "functions_streaming_event", Arguments.createMap().putMap("body", body)); + } + + private void emitDone(String appName, int listenerId) { + WritableMap body = Arguments.createMap(); + body.putBoolean("done", true); + emitEvent(appName, listenerId, body); + } } From 87b000a61eb10b9ce4786f8e1f4284ea283c9dad Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 11 Dec 2025 12:15:58 +0000 Subject: [PATCH 03/27] feat: typescript impl --- .../functions/__tests__/functions.test.ts | 28 +++++++++++ .../UniversalFirebaseFunctionsModule.java | 38 ++++++++++----- packages/functions/lib/modular.ts | 29 ++++++++++++ packages/functions/lib/namespaced.ts | 47 ++++++++++++++++++- packages/functions/lib/types/functions.ts | 8 ++++ 5 files changed, 137 insertions(+), 13 deletions(-) diff --git a/packages/functions/__tests__/functions.test.ts b/packages/functions/__tests__/functions.test.ts index d5baa3ebeb..053e967afa 100644 --- a/packages/functions/__tests__/functions.test.ts +++ b/packages/functions/__tests__/functions.test.ts @@ -7,6 +7,8 @@ import { httpsCallable, httpsCallableFromUrl, HttpsErrorCode, + httpsCallableStream, + httpsCallableFromUrlStream, type HttpsCallableOptions, type HttpsCallable as HttpsCallableType, type FunctionsModule, @@ -101,6 +103,14 @@ describe('Cloud Functions', function () { expect(HttpsErrorCode).toBeDefined(); }); + it('`httpsCallableStream` function is properly exposed to end user', function () { + expect(httpsCallableStream).toBeDefined(); + }); + + it('`httpsCallableFromUrlStream` function is properly exposed to end user', function () { + expect(httpsCallableFromUrlStream).toBeDefined(); + }); + describe('types', function () { it('`HttpsCallableOptions` type is properly exposed to end user', function () { const options: HttpsCallableOptions = { timeout: 5000 }; @@ -191,6 +201,24 @@ describe('Cloud Functions', function () { 'httpsCallableFromUrl', ); }); + + it('httpsCallableStream()', function () { + const functions = (getApp() as unknown as FirebaseApp).functions(); + functionsRefV9Deprecation( + () => httpsCallableStream(functions, 'example'), + () => functions.httpsCallableStream('example'), + 'httpsCallableStream', + ); + }); + + it('httpsCallableFromUrlStream()', function () { + const functions = (getApp() as unknown as FirebaseApp).functions(); + functionsRefV9Deprecation( + () => httpsCallableFromUrlStream(functions, 'https://example.com/example'), + () => functions.httpsCallableFromUrlStream('https://example.com/example'), + 'httpsCallableFromUrlStream', + ); + }); }); }); }); diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java index b68663cfa6..13c09d0ef7 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java @@ -134,19 +134,33 @@ Task httpsCallableStream( }); } - private void addFunctionsStreamingListener(String appName, int listenerId) { - functionsStreamingListeners.put(listenerId, null); - } - - private void removeFunctionsStreamingListener(String appName, int listenerId) { - functionsStreamingListeners.remove(listenerId); - } + Task httpsCallableStreamFromUrl( + String appName, + String region, + String host, + Integer port, + String url, + Object data, + ReadableMap options, + int listenerId) { - private void emitError(String appName, int listenerId, String message) { - WritableMap body = Arguments.createMap(); - body.putString("error", message != null ? message : "unknown_error"); - emitEvent(appName, listenerId, body); - } + return Tasks.call( + getExecutor(), + () -> { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseFunctions functionsInstance = FirebaseFunctions.getInstance(firebaseApp, region); + URL parsedUrl = new URL(url); + HttpsCallableReference httpReference = functionsInstance.getHttpsCallableFromUrl(parsedUrl); + httpReference.stream(data, options, (event) -> { + if (event.get("done")) { + removeFunctionsStreamingListener(appName, listenerId); + } else { + emitEvent(appName, listenerId, event); + } + }); + return Tasks.await(httpReference.call(data)).getData(); + }); + } private void emitEvent(String appName, int listenerId, WritableMap body) { ReactNativeFirebaseEventEmitter.emitEvent( diff --git a/packages/functions/lib/modular.ts b/packages/functions/lib/modular.ts index 873a3975c6..c99ef79950 100644 --- a/packages/functions/lib/modular.ts +++ b/packages/functions/lib/modular.ts @@ -95,3 +95,32 @@ export function httpsCallableFromUrl; } + + +export function httpsCallableStream( + functionsInstance: Functions, + name: string, + options?: HttpsCallableOptions, +): HttpsCallable { + return functionsInstance.httpsCallableStream.call( + functionsInstance, + name, + options, + // @ts-ignore + MODULAR_DEPRECATION_ARG, + ) as HttpsCallable; +} + +export function httpsCallableFromUrlStream( + functionsInstance: Functions, + url: string, + options?: HttpsCallableOptions, +): HttpsCallable { + return functionsInstance.httpsCallableFromUrlStream.call( + functionsInstance, + url, + options, + // @ts-ignore + MODULAR_DEPRECATION_ARG, + ) as HttpsCallable; +} diff --git a/packages/functions/lib/namespaced.ts b/packages/functions/lib/namespaced.ts index f64e92bba6..3862a12b14 100644 --- a/packages/functions/lib/namespaced.ts +++ b/packages/functions/lib/namespaced.ts @@ -270,7 +270,7 @@ class FirebaseFunctionsModule extends FirebaseModule { } } - return (data?: any) => { + const callableFunction: any = (data?: any) => { const nativePromise = this.native.httpsCallableFromUrl( this._useFunctionsEmulatorHost, this._useFunctionsEmulatorPort, @@ -292,6 +292,51 @@ class FirebaseFunctionsModule extends FirebaseModule { ); }); }; + + callableFunction.stream = (data?: any, onEvent?: (event: any) => void, streamOptions: HttpsCallableOptions = {}) => { + if (streamOptions.timeout) { + if (isNumber(streamOptions.timeout)) { + streamOptions.timeout = streamOptions.timeout / 1000; + } else { + throw new Error('HttpsCallableOptions.timeout expected a Number in milliseconds'); + } + } + + const listenerId = this._id_functions_streaming_event++; + // @ts-ignore + const eventName = this.eventNameForApp(`functions_streaming_event:${listenerId}`); + + // @ts-ignore + const subscription = this.emitter.addListener(eventName, (event: any) => { + const body = event.body; + if (onEvent) { + onEvent(body); + } + if (body && (body.done || body.error)) { + subscription.remove(); + if (this.native.removeFunctionsStreaming) { + this.native.removeFunctionsStreaming(listenerId); + } + } + }); + + this.native.httpsCallableStreamFromUrl( + this._useFunctionsEmulatorHost || null, + this._useFunctionsEmulatorPort || -1, + url, + { data }, + streamOptions, + listenerId, + ); + + return () => { + subscription.remove(); + if (this.native.removeFunctionsStreaming) { + this.native.removeFunctionsStreaming(listenerId); + } + }; + }; + return callableFunction; } useFunctionsEmulator(origin: string): void { diff --git a/packages/functions/lib/types/functions.ts b/packages/functions/lib/types/functions.ts index 16463d1b62..4cf27b2412 100644 --- a/packages/functions/lib/types/functions.ts +++ b/packages/functions/lib/types/functions.ts @@ -34,6 +34,14 @@ export interface FunctionsModule { url: string, options?: HttpsCallableOptions, ): HttpsCallable; + httpsCallableStream( + name: string, + options?: HttpsCallableOptions, + ): HttpsCallable; + httpsCallableStreamFromUrl( + url: string, + options?: HttpsCallableOptions, + ): HttpsCallable; useFunctionsEmulator(origin: string): void; useEmulator(host: string, port: number): void; } From b5aa190c52d464917f035d4498cbefbf649d35bf Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 11 Dec 2025 12:24:12 +0000 Subject: [PATCH 04/27] feat: turbomodules impl and use correct native method for android --- .../functions/NativeRNFBTurboFunctions.java | 45 +++++ .../UniversalFirebaseFunctionsModule.java | 187 +++++++++++++----- .../specs/NativeRNFBTurboFunctionsSpec.java | 12 ++ .../specs/NativeRNFBTurboFunctions.ts | 49 +++++ 4 files changed, 246 insertions(+), 47 deletions(-) diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java index 0e94711c53..523c8dac46 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java @@ -109,6 +109,51 @@ public void httpsCallableFromUrl( exception -> handleFunctionsException(exception, promise)); } + @Override + public void httpsCallableStream( + String appName, + String region, + String emulatorHost, + double emulatorPort, + String name, + ReadableMap data, + ReadableMap options, + double listenerId) { + + Object callableData = data.toHashMap().get(DATA_KEY); + + // Convert emulatorPort to Integer (null if not using emulator) + Integer port = emulatorHost != null ? (int) emulatorPort : null; + + module.httpsCallableStream( + appName, region, emulatorHost, port, name, callableData, options, (int) listenerId); + } + + @Override + public void httpsCallableStreamFromUrl( + String appName, + String region, + String emulatorHost, + double emulatorPort, + String url, + ReadableMap data, + ReadableMap options, + double listenerId) { + + Object callableData = data.toHashMap().get(DATA_KEY); + + // Convert emulatorPort to Integer (null if not using emulator) + Integer port = emulatorHost != null ? (int) emulatorPort : null; + + module.httpsCallableStreamFromUrl( + appName, region, emulatorHost, port, url, callableData, options, (int) listenerId); + } + + @Override + public void removeFunctionsStreaming(double listenerId) { + module.removeFunctionsStreamingListener((int) listenerId); + } + private void handleFunctionsException(Exception exception, Promise promise) { Object details = null; String code = "UNKNOWN"; diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java index 13c09d0ef7..35eb904331 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java @@ -31,14 +31,6 @@ import io.invertase.firebase.common.UniversalFirebaseModule; import java.net.URL; import java.util.concurrent.TimeUnit; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okio.BufferedSource; @SuppressWarnings("WeakerAccess") public class UniversalFirebaseFunctionsModule extends UniversalFirebaseModule { @@ -46,7 +38,8 @@ public class UniversalFirebaseFunctionsModule extends UniversalFirebaseModule { public static final String CODE_KEY = "code"; public static final String MSG_KEY = "message"; public static final String DETAILS_KEY = "details"; - private static SparseArray functionsStreamingListeners = new SparseArray<>(); + private static final String STREAMING_EVENT = "functions_streaming_event"; + private static SparseArray functionsStreamingListeners = new SparseArray<>(); UniversalFirebaseFunctionsModule(Context context, String serviceName) { super(context, serviceName); @@ -109,7 +102,7 @@ Task httpsCallableFromUrl( }); } - Task httpsCallableStream( + void httpsCallableStream( String appName, String region, String host, @@ -118,23 +111,57 @@ Task httpsCallableStream( Object data, ReadableMap options, int listenerId) { - return Tasks.call( - getExecutor(), - () -> { - FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); - FirebaseFunctions functionsInstance = FirebaseFunctions.getInstance(firebaseApp, region); - HttpsCallableReference httpReference = functionsInstance.getHttpsCallable.(name, data, options); - httpReference.stream(data, options, (event) -> { - if (event.get("done")) { - removeFunctionsStreamingListener(appName, listenerId); - } else { - emitEvent(appName, listenerId, event); - } - }); - }); + getExecutor() + .execute( + () -> { + try { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseFunctions functionsInstance = + FirebaseFunctions.getInstance(firebaseApp, region); + + if (host != null) { + functionsInstance.useEmulator(host, port); + } + + HttpsCallableReference httpReference = functionsInstance.getHttpsCallable(name); + + if (options.hasKey("timeout")) { + httpReference.setTimeout((long) options.getInt("timeout"), TimeUnit.SECONDS); + } + + // Store the listener reference + functionsStreamingListeners.put(listenerId, httpReference); + + // Use the Firebase SDK's native .stream() method + httpReference + .stream(data) + .addOnSuccessListener( + result -> { + // Emit the stream data as it arrives + emitStreamEvent(appName, listenerId, result.getData(), false, null); + }) + .addOnFailureListener( + exception -> { + // Emit error event + emitStreamEvent(appName, listenerId, null, true, exception.getMessage()); + removeFunctionsStreamingListener(listenerId); + }) + .addOnCompleteListener( + task -> { + // Stream completed - emit done event + if (task.isSuccessful()) { + emitStreamDone(appName, listenerId); + } + removeFunctionsStreamingListener(listenerId); + }); + } catch (Exception e) { + emitStreamEvent(appName, listenerId, null, true, e.getMessage()); + removeFunctionsStreamingListener(listenerId); + } + }); } - Task httpsCallableStreamFromUrl( + void httpsCallableStreamFromUrl( String appName, String region, String host, @@ -143,33 +170,99 @@ Task httpsCallableStreamFromUrl( Object data, ReadableMap options, int listenerId) { + getExecutor() + .execute( + () -> { + try { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseFunctions functionsInstance = + FirebaseFunctions.getInstance(firebaseApp, region); - return Tasks.call( - getExecutor(), - () -> { - FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); - FirebaseFunctions functionsInstance = FirebaseFunctions.getInstance(firebaseApp, region); - URL parsedUrl = new URL(url); - HttpsCallableReference httpReference = functionsInstance.getHttpsCallableFromUrl(parsedUrl); - httpReference.stream(data, options, (event) -> { - if (event.get("done")) { - removeFunctionsStreamingListener(appName, listenerId); - } else { - emitEvent(appName, listenerId, event); - } - }); - return Tasks.await(httpReference.call(data)).getData(); - }); - } + if (host != null) { + functionsInstance.useEmulator(host, port); + } + + URL parsedUrl = new URL(url); + HttpsCallableReference httpReference = + functionsInstance.getHttpsCallableFromUrl(parsedUrl); + + if (options.hasKey("timeout")) { + httpReference.setTimeout((long) options.getInt("timeout"), TimeUnit.SECONDS); + } + + // Store the listener reference + functionsStreamingListeners.put(listenerId, httpReference); + + // Use the Firebase SDK's native .stream() method + httpReference + .stream(data) + .addOnSuccessListener( + result -> { + // Emit the stream data as it arrives + emitStreamEvent(appName, listenerId, result.getData(), false, null); + }) + .addOnFailureListener( + exception -> { + // Emit error event + emitStreamEvent(appName, listenerId, null, true, exception.getMessage()); + removeFunctionsStreamingListener(listenerId); + }) + .addOnCompleteListener( + task -> { + // Stream completed - emit done event + if (task.isSuccessful()) { + emitStreamDone(appName, listenerId); + } + removeFunctionsStreamingListener(listenerId); + }); + } catch (Exception e) { + emitStreamEvent(appName, listenerId, null, true, e.getMessage()); + removeFunctionsStreamingListener(listenerId); + } + }); + } + + void removeFunctionsStreamingListener(int listenerId) { + Object listener = functionsStreamingListeners.get(listenerId); + if (listener != null) { + functionsStreamingListeners.remove(listenerId); + // Cancel/cleanup if needed + } + } - private void emitEvent(String appName, int listenerId, WritableMap body) { - ReactNativeFirebaseEventEmitter.emitEvent( - appName, "functions_streaming_event", Arguments.createMap().putMap("body", body)); + private void emitStreamEvent( + String appName, int listenerId, Object data, boolean isError, String errorMessage) { + WritableMap eventBody = Arguments.createMap(); + WritableMap body = Arguments.createMap(); + + if (isError) { + body.putString("error", errorMessage); + } else if (data != null) { + // Convert data to WritableMap/Array as needed + // Using RCTConvertFirebase from the common module + io.invertase.firebase.common.RCTConvertFirebase.mapPutValue("data", data, body); + } + + eventBody.putInt("listenerId", listenerId); + eventBody.putMap("body", body); + + FirebaseFunctionsStreamHandler handler = + new FirebaseFunctionsStreamHandler(STREAMING_EVENT, eventBody, appName, listenerId); + + ReactNativeFirebaseEventEmitter.getSharedInstance().sendEvent(handler); } - private void emitDone(String appName, int listenerId) { + private void emitStreamDone(String appName, int listenerId) { + WritableMap eventBody = Arguments.createMap(); WritableMap body = Arguments.createMap(); body.putBoolean("done", true); - emitEvent(appName, listenerId, body); + + eventBody.putInt("listenerId", listenerId); + eventBody.putMap("body", body); + + FirebaseFunctionsStreamHandler handler = + new FirebaseFunctionsStreamHandler(STREAMING_EVENT, eventBody, appName, listenerId); + + ReactNativeFirebaseEventEmitter.getSharedInstance().sendEvent(handler); } } diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java index 7784a71ade..ab4f440137 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java @@ -41,4 +41,16 @@ public NativeRNFBTurboFunctionsSpec(ReactApplicationContext reactContext) { @ReactMethod @DoNotStrip public abstract void httpsCallableFromUrl(String appName, String region, @Nullable String emulatorHost, double emulatorPort, String url, ReadableMap data, ReadableMap options, Promise promise); + + @ReactMethod + @DoNotStrip + public abstract void httpsCallableStream(String appName, String region, @Nullable String emulatorHost, double emulatorPort, String name, ReadableMap data, ReadableMap options, double listenerId); + + @ReactMethod + @DoNotStrip + public abstract void httpsCallableStreamFromUrl(String appName, String region, @Nullable String emulatorHost, double emulatorPort, String url, ReadableMap data, ReadableMap options, double listenerId); + + @ReactMethod + @DoNotStrip + public abstract void removeFunctionsStreaming(double listenerId); } diff --git a/packages/functions/specs/NativeRNFBTurboFunctions.ts b/packages/functions/specs/NativeRNFBTurboFunctions.ts index 63d60ba666..6b94ad4baa 100644 --- a/packages/functions/specs/NativeRNFBTurboFunctions.ts +++ b/packages/functions/specs/NativeRNFBTurboFunctions.ts @@ -45,6 +45,55 @@ export interface Spec extends TurboModule { data: { data: RequestData }, options: { timeout?: number }, ): Promise<{ data: ResponseData }>; + + /** + * Calls a Cloud Function with streaming support, emitting events as they arrive. + * + * @param emulatorHost - The emulator host (can be null) + * @param emulatorPort - The emulator port (can be -1 for no emulator) + * @param name - The name of the Cloud Function to call + * @param data - The data to pass to the function + * @param options - Additional options for the call + * @param listenerId - Unique identifier for this stream listener + */ + httpsCallableStream( + appName: string, + region: string, + emulatorHost: string | null, + emulatorPort: number, + name: string, + data: { data: RequestData }, + options: { timeout?: number }, + listenerId: number, + ): void; + + /** + * Calls a Cloud Function using a full URL with streaming support. + * + * @param emulatorHost - The emulator host (can be null) + * @param emulatorPort - The emulator port (can be -1 for no emulator) + * @param url - The full URL of the Cloud Function + * @param data - The data to pass to the function + * @param options - Additional options for the call + * @param listenerId - Unique identifier for this stream listener + */ + httpsCallableStreamFromUrl( + appName: string, + region: string, + emulatorHost: string | null, + emulatorPort: number, + url: string, + data: { data: RequestData }, + options: { timeout?: number }, + listenerId: number, + ): void; + + /** + * Removes/cancels a streaming listener. + * + * @param listenerId - The listener ID to remove + */ + removeFunctionsStreaming(listenerId: number): void; } export default TurboModuleRegistry.getEnforcing('NativeRNFBTurboFunctions'); From 20240415b5c811773a3390e710abc7573a9bda72 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 11 Dec 2025 15:24:21 +0000 Subject: [PATCH 05/27] feat: tests and event emission working --- .../workflows/scripts/functions/src/index.ts | 7 + .../functions/src/testStreamingCallable.ts | 117 ++++++ .../functions/NativeRNFBTurboFunctions.java | 2 +- .../UniversalFirebaseFunctionsModule.java | 153 +++++--- .../specs/NativeRNFBTurboFunctionsSpec.java | 2 +- .../NativeRNFBTurboFunctions-generated.cpp | 18 + .../NativeRNFBTurboFunctionsJSI-generated.cpp | 40 +++ .../NativeRNFBTurboFunctionsJSI.h | 27 ++ .../NativeRNFBTurboFunctions-generated.mm | 47 +++ .../NativeRNFBTurboFunctions.h | 99 ++++++ .../NativeRNFBTurboFunctionsJSI-generated.cpp | 40 +++ .../generated/NativeRNFBTurboFunctionsJSI.h | 27 ++ packages/functions/lib/modular.ts | 29 -- packages/functions/lib/namespaced.ts | 4 +- packages/functions/lib/types/functions.ts | 11 + .../specs/NativeRNFBTurboFunctions.ts | 4 +- .../functions/streaming-callable.tsx | 335 ++++++++++++++++++ tests/local-tests/index.js | 2 + 18 files changed, 876 insertions(+), 88 deletions(-) create mode 100644 .github/workflows/scripts/functions/src/testStreamingCallable.ts create mode 100644 tests/local-tests/functions/streaming-callable.tsx diff --git a/.github/workflows/scripts/functions/src/index.ts b/.github/workflows/scripts/functions/src/index.ts index 4d72f60ea0..a8a935ced6 100644 --- a/.github/workflows/scripts/functions/src/index.ts +++ b/.github/workflows/scripts/functions/src/index.ts @@ -31,3 +31,10 @@ export { fetchAppCheckTokenV2 } from './fetchAppCheckToken'; export { sendFCM } from './sendFCM'; export { testFetchStream, testFetch } from './vertexaiFunctions'; + +export { + testStreamingCallable, + testProgressStream, + testComplexDataStream, + testStreamWithError, +} from './testStreamingCallable'; diff --git a/.github/workflows/scripts/functions/src/testStreamingCallable.ts b/.github/workflows/scripts/functions/src/testStreamingCallable.ts new file mode 100644 index 0000000000..2415424800 --- /dev/null +++ b/.github/workflows/scripts/functions/src/testStreamingCallable.ts @@ -0,0 +1,117 @@ +import { onCall, CallableRequest } from 'firebase-functions/v2/https'; +import { logger } from 'firebase-functions/v2'; + +/** + * Test streaming callable function that sends multiple chunks of data + * This function demonstrates Server-Sent Events (SSE) streaming + */ +export const testStreamingCallable = onCall( + async (req: CallableRequest<{ count?: number; delay?: number }>) => { + const count = req.data.count || 5; + const delay = req.data.delay || 500; + + logger.info('testStreamingCallable called', { count, delay }); + + // For streaming, we need to return a stream response + // The Firebase SDK will handle the streaming protocol + const chunks = []; + for (let i = 0; i < count; i++) { + chunks.push({ + index: i, + message: `Chunk ${i + 1} of ${count}`, + timestamp: new Date().toISOString(), + data: { + value: i * 10, + isEven: i % 2 === 0, + }, + }); + } + + return { chunks, totalCount: count }; + }, +); + +/** + * Test streaming callable that sends progressive updates + */ +export const testProgressStream = onCall( + async (req: CallableRequest<{ task?: string }>) => { + const task = req.data.task || 'Processing'; + + logger.info('testProgressStream called', { task }); + + const updates = [ + { progress: 0, status: 'Starting...', task }, + { progress: 25, status: 'Loading data...', task }, + { progress: 50, status: 'Processing data...', task }, + { progress: 75, status: 'Finalizing...', task }, + { progress: 100, status: 'Complete!', task }, + ]; + + return { updates }; + }, +); + +/** + * Test streaming with complex data types + */ +export const testComplexDataStream = onCall(async (req: CallableRequest) => { + logger.info('testComplexDataStream called'); + + return { + items: [ + { + id: 1, + name: 'Item One', + tags: ['test', 'streaming', 'firebase'], + metadata: { + created: new Date().toISOString(), + version: '1.0.0', + }, + }, + { + id: 2, + name: 'Item Two', + tags: ['react-native', 'functions'], + metadata: { + created: new Date().toISOString(), + version: '1.0.1', + }, + }, + { + id: 3, + name: 'Item Three', + tags: ['cloud', 'streaming'], + metadata: { + created: new Date().toISOString(), + version: '2.0.0', + }, + }, + ], + summary: { + totalItems: 3, + processedAt: new Date().toISOString(), + }, + }; +}); + +/** + * Test streaming with error handling + */ +export const testStreamWithError = onCall( + async (req: CallableRequest<{ shouldError?: boolean; errorAfter?: number }>) => { + const shouldError = req.data.shouldError || false; + const errorAfter = req.data.errorAfter || 2; + + logger.info('testStreamWithError called', { shouldError, errorAfter }); + + if (shouldError) { + throw new Error('Simulated streaming error after chunk ' + errorAfter); + } + + return { + success: true, + message: 'No error occurred', + }; + }, +); diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java index 523c8dac46..b887c8a0c1 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java @@ -150,7 +150,7 @@ public void httpsCallableStreamFromUrl( } @Override - public void removeFunctionsStreaming(double listenerId) { + public void removeFunctionsStreaming(String appName, String region, double listenerId) { module.removeFunctionsStreamingListener((int) listenerId); } diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java index 35eb904331..58ef19a24d 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/UniversalFirebaseFunctionsModule.java @@ -27,10 +27,14 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.functions.FirebaseFunctions; import com.google.firebase.functions.HttpsCallableReference; +import com.google.firebase.functions.StreamResponse; import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter; import io.invertase.firebase.common.UniversalFirebaseModule; import java.net.URL; import java.util.concurrent.TimeUnit; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; @SuppressWarnings("WeakerAccess") public class UniversalFirebaseFunctionsModule extends UniversalFirebaseModule { @@ -115,12 +119,14 @@ void httpsCallableStream( .execute( () -> { try { + android.util.Log.d("RNFBFunctions", "httpsCallableStream starting for: " + name + ", listenerId: " + listenerId); FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseFunctions functionsInstance = FirebaseFunctions.getInstance(firebaseApp, region); if (host != null) { functionsInstance.useEmulator(host, port); + android.util.Log.d("RNFBFunctions", "Using emulator: " + host + ":" + port); } HttpsCallableReference httpReference = functionsInstance.getHttpsCallable(name); @@ -129,33 +135,51 @@ void httpsCallableStream( httpReference.setTimeout((long) options.getInt("timeout"), TimeUnit.SECONDS); } - // Store the listener reference - functionsStreamingListeners.put(listenerId, httpReference); - - // Use the Firebase SDK's native .stream() method - httpReference - .stream(data) - .addOnSuccessListener( - result -> { - // Emit the stream data as it arrives - emitStreamEvent(appName, listenerId, result.getData(), false, null); - }) - .addOnFailureListener( - exception -> { - // Emit error event - emitStreamEvent(appName, listenerId, null, true, exception.getMessage()); - removeFunctionsStreamingListener(listenerId); - }) - .addOnCompleteListener( - task -> { - // Stream completed - emit done event - if (task.isSuccessful()) { - emitStreamDone(appName, listenerId); - } - removeFunctionsStreamingListener(listenerId); - }); + android.util.Log.d("RNFBFunctions", "About to call .stream() method"); + // Use the Firebase SDK's native .stream() method which returns a Publisher + Publisher publisher = httpReference.stream(data); + android.util.Log.d("RNFBFunctions", "Stream publisher created successfully"); + + // Subscribe to the publisher + publisher.subscribe( + new Subscriber() { + private Subscription subscription; + + @Override + public void onSubscribe(Subscription s) { + subscription = s; + functionsStreamingListeners.put(listenerId, subscription); + s.request(Long.MAX_VALUE); // Request all items + } + + @Override + public void onNext(StreamResponse streamResponse) { + // Emit the stream data as it arrives + emitStreamEvent( + appName, listenerId, null, false, null); // TODO: Extract data from StreamResponse + } + + @Override + public void onError(Throwable t) { + // Emit error event + android.util.Log.e("RNFBFunctions", "Stream onError for " + name, t); + String errorMsg = t.getMessage() != null ? t.getMessage() : t.toString(); + emitStreamEvent(appName, listenerId, null, true, errorMsg); + removeFunctionsStreamingListener(listenerId); + } + + @Override + public void onComplete() { + // Stream completed - emit done event + android.util.Log.d("RNFBFunctions", "Stream onComplete for " + name); + emitStreamDone(appName, listenerId); + removeFunctionsStreamingListener(listenerId); + } + }); } catch (Exception e) { - emitStreamEvent(appName, listenerId, null, true, e.getMessage()); + android.util.Log.e("RNFBFunctions", "Exception in httpsCallableStream for " + name, e); + String errorMsg = e.getMessage() != null ? e.getMessage() : e.toString(); + emitStreamEvent(appName, listenerId, null, true, "Stream setup failed: " + errorMsg); removeFunctionsStreamingListener(listenerId); } }); @@ -174,12 +198,14 @@ void httpsCallableStreamFromUrl( .execute( () -> { try { + android.util.Log.d("RNFBFunctions", "httpsCallableStreamFromUrl starting for: " + url + ", listenerId: " + listenerId); FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseFunctions functionsInstance = FirebaseFunctions.getInstance(firebaseApp, region); if (host != null) { functionsInstance.useEmulator(host, port); + android.util.Log.d("RNFBFunctions", "Using emulator: " + host + ":" + port); } URL parsedUrl = new URL(url); @@ -190,33 +216,51 @@ void httpsCallableStreamFromUrl( httpReference.setTimeout((long) options.getInt("timeout"), TimeUnit.SECONDS); } - // Store the listener reference - functionsStreamingListeners.put(listenerId, httpReference); - - // Use the Firebase SDK's native .stream() method - httpReference - .stream(data) - .addOnSuccessListener( - result -> { - // Emit the stream data as it arrives - emitStreamEvent(appName, listenerId, result.getData(), false, null); - }) - .addOnFailureListener( - exception -> { - // Emit error event - emitStreamEvent(appName, listenerId, null, true, exception.getMessage()); - removeFunctionsStreamingListener(listenerId); - }) - .addOnCompleteListener( - task -> { - // Stream completed - emit done event - if (task.isSuccessful()) { - emitStreamDone(appName, listenerId); - } - removeFunctionsStreamingListener(listenerId); - }); + android.util.Log.d("RNFBFunctions", "About to call .stream() method on URL"); + // Use the Firebase SDK's native .stream() method which returns a Publisher + Publisher publisher = httpReference.stream(data); + android.util.Log.d("RNFBFunctions", "Stream publisher created successfully from URL"); + + // Subscribe to the publisher + publisher.subscribe( + new Subscriber() { + private Subscription subscription; + + @Override + public void onSubscribe(Subscription s) { + subscription = s; + functionsStreamingListeners.put(listenerId, subscription); + s.request(Long.MAX_VALUE); // Request all items + } + + @Override + public void onNext(StreamResponse streamResponse) { + // Emit the stream data as it arrives + emitStreamEvent( + appName, listenerId, null, false, null); // TODO: Extract data from StreamResponse + } + + @Override + public void onError(Throwable t) { + // Emit error event + android.util.Log.e("RNFBFunctions", "Stream onError for URL: " + url, t); + String errorMsg = t.getMessage() != null ? t.getMessage() : t.toString(); + emitStreamEvent(appName, listenerId, null, true, errorMsg); + removeFunctionsStreamingListener(listenerId); + } + + @Override + public void onComplete() { + // Stream completed - emit done event + android.util.Log.d("RNFBFunctions", "Stream onComplete for URL: " + url); + emitStreamDone(appName, listenerId); + removeFunctionsStreamingListener(listenerId); + } + }); } catch (Exception e) { - emitStreamEvent(appName, listenerId, null, true, e.getMessage()); + android.util.Log.e("RNFBFunctions", "Exception in httpsCallableStreamFromUrl for " + url, e); + String errorMsg = e.getMessage() != null ? e.getMessage() : e.toString(); + emitStreamEvent(appName, listenerId, null, true, "Stream setup failed: " + errorMsg); removeFunctionsStreamingListener(listenerId); } }); @@ -225,8 +269,11 @@ void httpsCallableStreamFromUrl( void removeFunctionsStreamingListener(int listenerId) { Object listener = functionsStreamingListeners.get(listenerId); if (listener != null) { + // Cancel the subscription if it's still active + if (listener instanceof Subscription) { + ((Subscription) listener).cancel(); + } functionsStreamingListeners.remove(listenerId); - // Cancel/cleanup if needed } } diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java index ab4f440137..a2c5c2109c 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java @@ -52,5 +52,5 @@ public NativeRNFBTurboFunctionsSpec(ReactApplicationContext reactContext) { @ReactMethod @DoNotStrip - public abstract void removeFunctionsStreaming(double listenerId); + public abstract void removeFunctionsStreaming(String appName, String region, double listenerId); } diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions-generated.cpp b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions-generated.cpp index 2c35a4c6fe..63263ee488 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions-generated.cpp +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions-generated.cpp @@ -22,10 +22,28 @@ static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_https return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, "httpsCallableFromUrl", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId); } +static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStream(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "httpsCallableStream", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;D)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStreamFromUrl(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "httpsCallableStreamFromUrl", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;D)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_removeFunctionsStreaming(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "removeFunctionsStreaming", "(Ljava/lang/String;Ljava/lang/String;D)V", args, count, cachedMethodId); +} + NativeRNFBTurboFunctionsSpecJSI::NativeRNFBTurboFunctionsSpecJSI(const JavaTurboModule::InitParams ¶ms) : JavaTurboModule(params) { methodMap_["httpsCallable"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallable}; methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableFromUrl}; + methodMap_["httpsCallableStream"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStream}; + methodMap_["httpsCallableStreamFromUrl"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStreamFromUrl}; + methodMap_["removeFunctionsStreaming"] = MethodMetadata {3, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_removeFunctionsStreaming}; } std::shared_ptr NativeRNFBTurboFunctions_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI-generated.cpp b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI-generated.cpp index c892ffbf8b..31cddefdf8 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI-generated.cpp +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI-generated.cpp @@ -35,11 +35,51 @@ static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallabl count <= 6 ? throw jsi::JSError(rt, "Expected argument in position 6 to be passed") : args[6].asObject(rt) ); } +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStream(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->httpsCallableStream( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), + count <= 2 || args[2].isNull() || args[2].isUndefined() ? std::nullopt : std::make_optional(args[2].asString(rt)), + count <= 3 ? throw jsi::JSError(rt, "Expected argument in position 3 to be passed") : args[3].asNumber(), + count <= 4 ? throw jsi::JSError(rt, "Expected argument in position 4 to be passed") : args[4].asString(rt), + count <= 5 ? throw jsi::JSError(rt, "Expected argument in position 5 to be passed") : args[5].asObject(rt), + count <= 6 ? throw jsi::JSError(rt, "Expected argument in position 6 to be passed") : args[6].asObject(rt), + count <= 7 ? throw jsi::JSError(rt, "Expected argument in position 7 to be passed") : args[7].asNumber() + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStreamFromUrl(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->httpsCallableStreamFromUrl( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), + count <= 2 || args[2].isNull() || args[2].isUndefined() ? std::nullopt : std::make_optional(args[2].asString(rt)), + count <= 3 ? throw jsi::JSError(rt, "Expected argument in position 3 to be passed") : args[3].asNumber(), + count <= 4 ? throw jsi::JSError(rt, "Expected argument in position 4 to be passed") : args[4].asString(rt), + count <= 5 ? throw jsi::JSError(rt, "Expected argument in position 5 to be passed") : args[5].asObject(rt), + count <= 6 ? throw jsi::JSError(rt, "Expected argument in position 6 to be passed") : args[6].asObject(rt), + count <= 7 ? throw jsi::JSError(rt, "Expected argument in position 7 to be passed") : args[7].asNumber() + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_removeFunctionsStreaming(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->removeFunctionsStreaming( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), + count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asNumber() + ); + return jsi::Value::undefined(); +} NativeRNFBTurboFunctionsCxxSpecJSI::NativeRNFBTurboFunctionsCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("NativeRNFBTurboFunctions", jsInvoker) { methodMap_["httpsCallable"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallable}; methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableFromUrl}; + methodMap_["httpsCallableStream"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStream}; + methodMap_["httpsCallableStreamFromUrl"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStreamFromUrl}; + methodMap_["removeFunctionsStreaming"] = MethodMetadata {3, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_removeFunctionsStreaming}; } diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI.h b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI.h index 2cd49cbaee..4e9f4594a7 100644 --- a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI.h +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI.h @@ -22,6 +22,9 @@ namespace facebook::react { public: virtual jsi::Value httpsCallable(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String name, jsi::Object data, jsi::Object options) = 0; virtual jsi::Value httpsCallableFromUrl(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String url, jsi::Object data, jsi::Object options) = 0; + virtual void httpsCallableStream(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String name, jsi::Object data, jsi::Object options, double listenerId) = 0; + virtual void httpsCallableStreamFromUrl(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String url, jsi::Object data, jsi::Object options, double listenerId) = 0; + virtual void removeFunctionsStreaming(jsi::Runtime &rt, jsi::String appName, jsi::String region, double listenerId) = 0; }; @@ -68,6 +71,30 @@ class JSI_EXPORT NativeRNFBTurboFunctionsCxxSpec : public TurboModule { return bridging::callFromJs( rt, &T::httpsCallableFromUrl, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(url), std::move(data), std::move(options)); } + void httpsCallableStream(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String name, jsi::Object data, jsi::Object options, double listenerId) override { + static_assert( + bridging::getParameterCount(&T::httpsCallableStream) == 9, + "Expected httpsCallableStream(...) to have 9 parameters"); + + return bridging::callFromJs( + rt, &T::httpsCallableStream, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(name), std::move(data), std::move(options), std::move(listenerId)); + } + void httpsCallableStreamFromUrl(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String url, jsi::Object data, jsi::Object options, double listenerId) override { + static_assert( + bridging::getParameterCount(&T::httpsCallableStreamFromUrl) == 9, + "Expected httpsCallableStreamFromUrl(...) to have 9 parameters"); + + return bridging::callFromJs( + rt, &T::httpsCallableStreamFromUrl, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(url), std::move(data), std::move(options), std::move(listenerId)); + } + void removeFunctionsStreaming(jsi::Runtime &rt, jsi::String appName, jsi::String region, double listenerId) override { + static_assert( + bridging::getParameterCount(&T::removeFunctionsStreaming) == 4, + "Expected removeFunctionsStreaming(...) to have 4 parameters"); + + return bridging::callFromJs( + rt, &T::removeFunctionsStreaming, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(listenerId)); + } private: friend class NativeRNFBTurboFunctionsCxxSpec; diff --git a/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions-generated.mm b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions-generated.mm index 7fff3b0c74..04c89d0132 100644 --- a/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions-generated.mm +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions-generated.mm @@ -47,6 +47,30 @@ + (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptio return facebook::react::managedPointer(json); } @end +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamData:(id)json +{ + return facebook::react::managedPointer(json); +} +@end +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamOptions:(id)json +{ + return facebook::react::managedPointer(json); +} +@end +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlData:(id)json +{ + return facebook::react::managedPointer(json); +} +@end +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlOptions:(id)json +{ + return facebook::react::managedPointer(json); +} +@end namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallable(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { @@ -57,6 +81,18 @@ + (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptio return static_cast(turboModule).invokeObjCMethod(rt, PromiseKind, "httpsCallableFromUrl", @selector(httpsCallableFromUrl:region:emulatorHost:emulatorPort:url:data:options:resolve:reject:), args, count); } + static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStream(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "httpsCallableStream", @selector(httpsCallableStream:region:emulatorHost:emulatorPort:name:data:options:listenerId:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStreamFromUrl(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "httpsCallableStreamFromUrl", @selector(httpsCallableStreamFromUrl:region:emulatorHost:emulatorPort:url:data:options:listenerId:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_removeFunctionsStreaming(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "removeFunctionsStreaming", @selector(removeFunctionsStreaming:region:listenerId:), args, count); + } + NativeRNFBTurboFunctionsSpecJSI::NativeRNFBTurboFunctionsSpecJSI(const ObjCTurboModule::InitParams ¶ms) : ObjCTurboModule(params) { @@ -67,5 +103,16 @@ + (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptio methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableFromUrl}; setMethodArgConversionSelector(@"httpsCallableFromUrl", 5, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlData:"); setMethodArgConversionSelector(@"httpsCallableFromUrl", 6, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions:"); + + methodMap_["httpsCallableStream"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStream}; + setMethodArgConversionSelector(@"httpsCallableStream", 5, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamData:"); + setMethodArgConversionSelector(@"httpsCallableStream", 6, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamOptions:"); + + methodMap_["httpsCallableStreamFromUrl"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableStreamFromUrl}; + setMethodArgConversionSelector(@"httpsCallableStreamFromUrl", 5, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlData:"); + setMethodArgConversionSelector(@"httpsCallableStreamFromUrl", 6, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlOptions:"); + + methodMap_["removeFunctionsStreaming"] = MethodMetadata {3, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_removeFunctionsStreaming}; + } } // namespace facebook::react diff --git a/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions.h b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions.h index f7e34a1777..030f194534 100644 --- a/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions.h +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions.h @@ -92,6 +92,66 @@ namespace JS { @interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions) + (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions:(id)json; @end +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableStreamData { + id data() const; + + SpecHttpsCallableStreamData(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamData:(id)json; +@end +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableStreamOptions { + std::optional timeout() const; + + SpecHttpsCallableStreamOptions(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamOptions:(id)json; +@end +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableStreamFromUrlData { + id data() const; + + SpecHttpsCallableStreamFromUrlData(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlData:(id)json; +@end +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableStreamFromUrlOptions { + std::optional timeout() const; + + SpecHttpsCallableStreamFromUrlOptions(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableStreamFromUrlOptions:(id)json; +@end @protocol NativeRNFBTurboFunctionsSpec - (void)httpsCallable:(NSString *)appName @@ -112,6 +172,25 @@ namespace JS { options:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableFromUrlOptions &)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; +- (void)httpsCallableStream:(NSString *)appName + region:(NSString *)region + emulatorHost:(NSString * _Nullable)emulatorHost + emulatorPort:(double)emulatorPort + name:(NSString *)name + data:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamData &)data + options:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamOptions &)options + listenerId:(double)listenerId; +- (void)httpsCallableStreamFromUrl:(NSString *)appName + region:(NSString *)region + emulatorHost:(NSString * _Nullable)emulatorHost + emulatorPort:(double)emulatorPort + url:(NSString *)url + data:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamFromUrlData &)data + options:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamFromUrlOptions &)options + listenerId:(double)listenerId; +- (void)removeFunctionsStreaming:(NSString *)appName + region:(NSString *)region + listenerId:(double)listenerId; @end @@ -153,5 +232,25 @@ inline std::optional JS::NativeRNFBTurboFunctions::SpecHttpsCallableFrom id const p = _v[@"timeout"]; return RCTBridgingToOptionalDouble(p); } +inline id JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamData::data() const +{ + id const p = _v[@"data"]; + return p; +} +inline std::optional JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamOptions::timeout() const +{ + id const p = _v[@"timeout"]; + return RCTBridgingToOptionalDouble(p); +} +inline id JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamFromUrlData::data() const +{ + id const p = _v[@"data"]; + return p; +} +inline std::optional JS::NativeRNFBTurboFunctions::SpecHttpsCallableStreamFromUrlOptions::timeout() const +{ + id const p = _v[@"timeout"]; + return RCTBridgingToOptionalDouble(p); +} NS_ASSUME_NONNULL_END #endif // NativeRNFBTurboFunctions_H diff --git a/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI-generated.cpp b/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI-generated.cpp index c892ffbf8b..31cddefdf8 100644 --- a/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI-generated.cpp +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI-generated.cpp @@ -35,11 +35,51 @@ static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallabl count <= 6 ? throw jsi::JSError(rt, "Expected argument in position 6 to be passed") : args[6].asObject(rt) ); } +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStream(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->httpsCallableStream( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), + count <= 2 || args[2].isNull() || args[2].isUndefined() ? std::nullopt : std::make_optional(args[2].asString(rt)), + count <= 3 ? throw jsi::JSError(rt, "Expected argument in position 3 to be passed") : args[3].asNumber(), + count <= 4 ? throw jsi::JSError(rt, "Expected argument in position 4 to be passed") : args[4].asString(rt), + count <= 5 ? throw jsi::JSError(rt, "Expected argument in position 5 to be passed") : args[5].asObject(rt), + count <= 6 ? throw jsi::JSError(rt, "Expected argument in position 6 to be passed") : args[6].asObject(rt), + count <= 7 ? throw jsi::JSError(rt, "Expected argument in position 7 to be passed") : args[7].asNumber() + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStreamFromUrl(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->httpsCallableStreamFromUrl( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), + count <= 2 || args[2].isNull() || args[2].isUndefined() ? std::nullopt : std::make_optional(args[2].asString(rt)), + count <= 3 ? throw jsi::JSError(rt, "Expected argument in position 3 to be passed") : args[3].asNumber(), + count <= 4 ? throw jsi::JSError(rt, "Expected argument in position 4 to be passed") : args[4].asString(rt), + count <= 5 ? throw jsi::JSError(rt, "Expected argument in position 5 to be passed") : args[5].asObject(rt), + count <= 6 ? throw jsi::JSError(rt, "Expected argument in position 6 to be passed") : args[6].asObject(rt), + count <= 7 ? throw jsi::JSError(rt, "Expected argument in position 7 to be passed") : args[7].asNumber() + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_removeFunctionsStreaming(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->removeFunctionsStreaming( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), + count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asNumber() + ); + return jsi::Value::undefined(); +} NativeRNFBTurboFunctionsCxxSpecJSI::NativeRNFBTurboFunctionsCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule("NativeRNFBTurboFunctions", jsInvoker) { methodMap_["httpsCallable"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallable}; methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableFromUrl}; + methodMap_["httpsCallableStream"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStream}; + methodMap_["httpsCallableStreamFromUrl"] = MethodMetadata {8, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableStreamFromUrl}; + methodMap_["removeFunctionsStreaming"] = MethodMetadata {3, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_removeFunctionsStreaming}; } diff --git a/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI.h b/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI.h index 2cd49cbaee..4e9f4594a7 100644 --- a/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI.h +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI.h @@ -22,6 +22,9 @@ namespace facebook::react { public: virtual jsi::Value httpsCallable(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String name, jsi::Object data, jsi::Object options) = 0; virtual jsi::Value httpsCallableFromUrl(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String url, jsi::Object data, jsi::Object options) = 0; + virtual void httpsCallableStream(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String name, jsi::Object data, jsi::Object options, double listenerId) = 0; + virtual void httpsCallableStreamFromUrl(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String url, jsi::Object data, jsi::Object options, double listenerId) = 0; + virtual void removeFunctionsStreaming(jsi::Runtime &rt, jsi::String appName, jsi::String region, double listenerId) = 0; }; @@ -68,6 +71,30 @@ class JSI_EXPORT NativeRNFBTurboFunctionsCxxSpec : public TurboModule { return bridging::callFromJs( rt, &T::httpsCallableFromUrl, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(url), std::move(data), std::move(options)); } + void httpsCallableStream(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String name, jsi::Object data, jsi::Object options, double listenerId) override { + static_assert( + bridging::getParameterCount(&T::httpsCallableStream) == 9, + "Expected httpsCallableStream(...) to have 9 parameters"); + + return bridging::callFromJs( + rt, &T::httpsCallableStream, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(name), std::move(data), std::move(options), std::move(listenerId)); + } + void httpsCallableStreamFromUrl(jsi::Runtime &rt, jsi::String appName, jsi::String region, std::optional emulatorHost, double emulatorPort, jsi::String url, jsi::Object data, jsi::Object options, double listenerId) override { + static_assert( + bridging::getParameterCount(&T::httpsCallableStreamFromUrl) == 9, + "Expected httpsCallableStreamFromUrl(...) to have 9 parameters"); + + return bridging::callFromJs( + rt, &T::httpsCallableStreamFromUrl, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(url), std::move(data), std::move(options), std::move(listenerId)); + } + void removeFunctionsStreaming(jsi::Runtime &rt, jsi::String appName, jsi::String region, double listenerId) override { + static_assert( + bridging::getParameterCount(&T::removeFunctionsStreaming) == 4, + "Expected removeFunctionsStreaming(...) to have 4 parameters"); + + return bridging::callFromJs( + rt, &T::removeFunctionsStreaming, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(listenerId)); + } private: friend class NativeRNFBTurboFunctionsCxxSpec; diff --git a/packages/functions/lib/modular.ts b/packages/functions/lib/modular.ts index c99ef79950..873a3975c6 100644 --- a/packages/functions/lib/modular.ts +++ b/packages/functions/lib/modular.ts @@ -95,32 +95,3 @@ export function httpsCallableFromUrl; } - - -export function httpsCallableStream( - functionsInstance: Functions, - name: string, - options?: HttpsCallableOptions, -): HttpsCallable { - return functionsInstance.httpsCallableStream.call( - functionsInstance, - name, - options, - // @ts-ignore - MODULAR_DEPRECATION_ARG, - ) as HttpsCallable; -} - -export function httpsCallableFromUrlStream( - functionsInstance: Functions, - url: string, - options?: HttpsCallableOptions, -): HttpsCallable { - return functionsInstance.httpsCallableFromUrlStream.call( - functionsInstance, - url, - options, - // @ts-ignore - MODULAR_DEPRECATION_ARG, - ) as HttpsCallable; -} diff --git a/packages/functions/lib/namespaced.ts b/packages/functions/lib/namespaced.ts index 3862a12b14..b86ca84f3e 100644 --- a/packages/functions/lib/namespaced.ts +++ b/packages/functions/lib/namespaced.ts @@ -75,8 +75,6 @@ const statics = { HttpsErrorCode, }; -const nativeEvents = ['functions_streaming_event']; - // Export the complete FirebaseFunctionsTypes namespace // eslint-disable-next-line @typescript-eslint/no-namespace export namespace FirebaseFunctionsTypes { @@ -390,7 +388,7 @@ export default createModuleNamespace({ version, namespace, nativeModuleName, - nativeEvents: false, + nativeEvents: ['functions_streaming_event'], hasMultiAppSupport: true, hasCustomUrlOrRegionSupport: true, ModuleClass: FirebaseFunctionsModule, diff --git a/packages/functions/lib/types/functions.ts b/packages/functions/lib/types/functions.ts index 4cf27b2412..4c12a5442a 100644 --- a/packages/functions/lib/types/functions.ts +++ b/packages/functions/lib/types/functions.ts @@ -21,8 +21,19 @@ export interface HttpsCallableOptions { timeout?: number; } +export interface StreamEvent { + data?: ResponseData; + error?: string; + done?: boolean; +} + export interface HttpsCallable { (data?: RequestData | null): Promise<{ data: ResponseData }>; + stream( + data?: RequestData | null, + onEvent?: (event: StreamEvent) => void, + options?: HttpsCallableOptions, + ): () => void; } export interface FunctionsModule { diff --git a/packages/functions/specs/NativeRNFBTurboFunctions.ts b/packages/functions/specs/NativeRNFBTurboFunctions.ts index 6b94ad4baa..3d2a74849b 100644 --- a/packages/functions/specs/NativeRNFBTurboFunctions.ts +++ b/packages/functions/specs/NativeRNFBTurboFunctions.ts @@ -91,9 +91,11 @@ export interface Spec extends TurboModule { /** * Removes/cancels a streaming listener. * + * @param appName - The app name + * @param region - The region * @param listenerId - The listener ID to remove */ - removeFunctionsStreaming(listenerId: number): void; + removeFunctionsStreaming(appName: string, region: string, listenerId: number): void; } export default TurboModuleRegistry.getEnforcing('NativeRNFBTurboFunctions'); diff --git a/tests/local-tests/functions/streaming-callable.tsx b/tests/local-tests/functions/streaming-callable.tsx new file mode 100644 index 0000000000..315343e2be --- /dev/null +++ b/tests/local-tests/functions/streaming-callable.tsx @@ -0,0 +1,335 @@ +import React, { useState } from 'react'; +import { Button, Text, View, ScrollView, StyleSheet } from 'react-native'; + +import { getApp } from '@react-native-firebase/app'; +import { + getFunctions, + connectFunctionsEmulator, + httpsCallable, + httpsCallableFromUrl, +} from '@react-native-firebase/functions'; + +const functions = getFunctions(); +connectFunctionsEmulator(functions, 'localhost', 5001); + +export function StreamingCallableTestComponent(): React.JSX.Element { + const [logs, setLogs] = useState([]); + const [isStreaming, setIsStreaming] = useState(false); + const [stopFunction, setStopFunction] = useState<(() => void) | null>(null); + + const addLog = (message: string) => { + const timestamp = new Date().toLocaleTimeString(); + setLogs(prev => [`[${timestamp}] ${message}`, ...prev].slice(0, 50)); + console.log(`[StreamingTest] ${message}`); + }; + + const clearLogs = () => { + setLogs([]); + }; + + const testBasicStream = async () => { + try { + addLog('🚀 Starting basic streaming test...'); + setIsStreaming(true); + + const functionRunner = httpsCallable(functions, 'testStreamingCallable'); + + // Use the .stream() method + const stop = functionRunner.stream( + { count: 5, delay: 500 }, + event => { + addLog(`📦 Received event: ${JSON.stringify(event)}`); + + if (event.done) { + addLog('✅ Stream completed'); + setIsStreaming(false); + setStopFunction(null); + } else if (event.error) { + addLog(`❌ Stream error: ${event.error}`); + setIsStreaming(false); + setStopFunction(null); + } else if (event.data) { + addLog(`📊 Data chunk: ${JSON.stringify(event.data)}`); + } + }, + ); + + setStopFunction(() => stop); + } catch (e: any) { + addLog(`❌ Error: ${e.message}`); + setIsStreaming(false); + } + }; + + const testProgressStream = async () => { + try { + addLog('📈 Starting progress streaming test...'); + setIsStreaming(true); + + const functionRunner = httpsCallable(functions, 'testProgressStream'); + + const stop = functionRunner.stream({ task: 'TestTask' }, event => { + if (event.done) { + addLog('✅ Progress stream completed'); + setIsStreaming(false); + setStopFunction(null); + } else if (event.error) { + addLog(`❌ Progress error: ${event.error}`); + setIsStreaming(false); + setStopFunction(null); + } else if (event.data) { + const data = event.data; + if (data.progress !== undefined) { + addLog(`⏳ Progress: ${data.progress}% - ${data.status}`); + } else { + addLog(`📊 Progress data: ${JSON.stringify(data)}`); + } + } + }); + + setStopFunction(() => stop); + } catch (e: any) { + addLog(`❌ Error: ${e.message}`); + setIsStreaming(false); + } + }; + + const testComplexDataStream = async () => { + try { + addLog('🔧 Starting complex data streaming test...'); + setIsStreaming(true); + + const functionRunner = httpsCallable(functions, 'testComplexDataStream'); + + const stop = functionRunner.stream({}, event => { + if (event.done) { + addLog('✅ Complex data stream completed'); + setIsStreaming(false); + setStopFunction(null); + } else if (event.error) { + addLog(`❌ Complex data error: ${event.error}`); + setIsStreaming(false); + setStopFunction(null); + } else if (event.data) { + addLog(`🗂️ Complex data: ${JSON.stringify(event.data, null, 2)}`); + } + }); + + setStopFunction(() => stop); + } catch (e: any) { + addLog(`❌ Error: ${e.message}`); + setIsStreaming(false); + } + }; + + const testStreamFromUrl = async () => { + try { + addLog('🌐 Testing httpsCallableFromUrl streaming...'); + setIsStreaming(true); + + // Test httpsCallableFromUrl streaming with modular API + // For emulator: http://localhost:5001/{projectId}/{region}/{functionName} + const url = 'http://localhost:5001/test-project/us-central1/testStreamingCallable'; + const callableRef = httpsCallableFromUrl(functions, url); + + const stop = callableRef.stream({ count: 3, delay: 400 }, event => { + if (event.done) { + addLog('✅ URL stream completed'); + setIsStreaming(false); + setStopFunction(null); + } else if (event.error) { + addLog(`❌ URL stream error: ${event.error}`); + setIsStreaming(false); + setStopFunction(null); + } else if (event.data) { + addLog(`📦 URL data: ${JSON.stringify(event.data)}`); + } + }); + + setStopFunction(() => stop); + } catch (e: any) { + addLog(`❌ Error: ${e.message}`); + setIsStreaming(false); + } + }; + + const testStreamWithOptions = async () => { + try { + addLog('⚙️ Testing stream with timeout option...'); + setIsStreaming(true); + + const callableRef = httpsCallable(functions, 'testStreamingCallable'); + + const stop = callableRef.stream( + { count: 3 }, + event => { + if (event.done) { + addLog('✅ Options stream completed'); + setIsStreaming(false); + setStopFunction(null); + } else if (event.error) { + addLog(`❌ Options stream error: ${event.error}`); + setIsStreaming(false); + setStopFunction(null); + } else if (event.data) { + addLog(`📦 Options data: ${JSON.stringify(event.data)}`); + } + }, + { timeout: 30000 } // 30 second timeout + ); + + setStopFunction(() => stop); + } catch (e: any) { + addLog(`❌ Error: ${e.message}`); + setIsStreaming(false); + } + }; + + const stopStream = () => { + if (stopFunction) { + addLog('🛑 Stopping stream...'); + stopFunction(); + setStopFunction(null); + setIsStreaming(false); + } + }; + + return ( + + 🌊 Cloud Functions Streaming Tests + Ensure Emulator is running on localhost:5001 + + +