diff --git a/eslint.config.mjs b/eslint.config.mjs index 36ad9e0c93..165d111b22 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -28,6 +28,7 @@ export default [ '**/app.playground.js', '**/type-test.ts', 'packages/**/modular/dist/**/*', + 'packages/**/dist/**/*', 'src/version.js', 'packages/**/node_modules/**/*', 'packages/**/plugin/build/**/*', @@ -40,6 +41,7 @@ export default [ '**/type-test.ts', 'packages/**/modular/dist/**/*', 'packages/ai/__tests__/test-utils', + 'packages/vertexai/__tests__/test-utils', 'packages/vertexai/dist', 'packages/ai/dist', ], diff --git a/package.json b/package.json index 51f65d8fb7..5fba379641 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,13 @@ "lerna:clean": "lerna clean", "build:all:clean": "lerna run build:clean", "build:all:build": "lerna run build", + "test:functions:build": "cd .github/workflows/scripts/functions && yarn build", + "codegen:all": "lerna run codegen", "lint": "yarn lint:js && yarn lint:android && yarn lint:ios:check", "lint:js": "eslint packages/* --max-warnings=0", - "lint:android": "(google-java-format --set-exit-if-changed --replace --glob=\"packages/*/android/src/**/*.java\" || (echo \"\n\nandroid formatting error - please re-run\n\n\" && exit 1)) && (git diff --exit-code packages/*/android/src || (echo \"\n\nandroid files changed from linting, please examine and commit result\n\n\" && exit 1))", - "lint:ios:check": "clang-format --glob=\"packages/*/ios/**/*.{h,cpp,m,mm}\" --style=Google -n -Werror", - "lint:ios:fix": "clang-format -i --glob=\"packages/*/ios/**/*.{h,cpp,m,mm}\" --style=Google", + "lint:android": "(find packages/*/android/src -name '*.java' -not -path '*/generated/*' -print0 | xargs -0 google-java-format --set-exit-if-changed --replace || (echo \"\n\nandroid formatting error - please re-run\n\n\" && exit 1)) && (git diff --exit-code packages/*/android/src || (echo \"\n\nandroid files changed from linting, please examine and commit result\n\n\" && exit 1))", + "lint:ios:check": "find packages/*/ios -type f \\( -name '*.h' -o -name '*.cpp' -o -name '*.m' -o -name '*.mm' \\) -not -path '*/generated/*' -print0 | xargs -0 clang-format --style=Google -n -Werror", + "lint:ios:fix": "find packages/*/ios -type f \\( -name '*.h' -o -name '*.cpp' -o -name '*.m' -o -name '*.mm' \\) -not -path '*/generated/*' -print0 | xargs -0 clang-format -i --style=Google", "lint:markdown": "prettier --check \"docs/**/*.md\"", "lint:report": "eslint --output-file=eslint-report.json --format=json . --ext .js,.jsx,.ts,.tsx", "lint:spellcheck": "spellchecker --quiet --files=\"docs/**/*.md\" --dictionaries=\"./.spellcheck.dict.txt\" --reports=\"spelling.json\" --plugins spell indefinite-article repeated-words syntax-mentions syntax-urls frontmatter", @@ -71,6 +73,7 @@ "@firebase/rules-unit-testing": "^5.0.0", "@inquirer/prompts": "^7.10.0", "@octokit/core": "^7.0.6", + "@react-native-community/cli": "latest", "@tsconfig/node-lts": "^22.0.4", "@types/react": "~19.0.14", "@types/react-native": "^0.73.0", diff --git a/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.java b/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.java index bcb4274a9e..740f119e34 100644 --- a/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.java +++ b/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.java @@ -29,7 +29,7 @@ public class UniversalFirebaseModule { private final Context context; private final String serviceName; - protected UniversalFirebaseModule(Context context, String serviceName) { + public UniversalFirebaseModule(Context context, String serviceName) { this.context = context; this.serviceName = serviceName; this.executorService = new TaskExecutorService(getName()); @@ -43,7 +43,7 @@ public Context getApplicationContext() { return getContext().getApplicationContext(); } - protected ExecutorService getExecutor() { + public ExecutorService getExecutor() { return executorService.getExecutor(); } diff --git a/packages/app/ios/RNFBApp.xcodeproj/project.pbxproj b/packages/app/ios/RNFBApp.xcodeproj/project.pbxproj index a43417d839..cf22fc572d 100644 --- a/packages/app/ios/RNFBApp.xcodeproj/project.pbxproj +++ b/packages/app/ios/RNFBApp.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 2748D8202237018600FC8DC8 /* RNFBMeta.m in Sources */ = {isa = PBXBuildFile; fileRef = 2748D81F2237018600FC8DC8 /* RNFBMeta.m */; }; 4D97BAD423042F2700077358 /* RNFBUtilsModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D97BAD323042F2700077358 /* RNFBUtilsModule.m */; }; DAA1F28522FCF6AD00F4DEC1 /* RNFBVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA1F28422FCF6AD00F4DEC1 /* RNFBVersion.m */; }; + RNFB00001A2025011100000001 /* RNFBNullSentinelInterceptor.m in Sources */ = {isa = PBXBuildFile; fileRef = RNFB00001A2025011100000002 /* RNFBNullSentinelInterceptor.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -53,6 +54,8 @@ 4D97BAD323042F2700077358 /* RNFBUtilsModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFBUtilsModule.m; sourceTree = ""; }; DAA1F28422FCF6AD00F4DEC1 /* RNFBVersion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFBVersion.m; sourceTree = ""; }; DAA1F28622FCF6C200F4DEC1 /* RNFBVersion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFBVersion.h; sourceTree = ""; }; + RNFB00001A2025011100000002 /* RNFBNullSentinelInterceptor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFBNullSentinelInterceptor.m; sourceTree = ""; }; + RNFB00001A2025011100000003 /* RNFBNullSentinelInterceptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFBNullSentinelInterceptor.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -97,6 +100,8 @@ DAA1F28622FCF6C200F4DEC1 /* RNFBVersion.h */, 4D97BAD223042F0800077358 /* RNFBUtilsModule.h */, 4D97BAD323042F2700077358 /* RNFBUtilsModule.m */, + RNFB00001A2025011100000003 /* RNFBNullSentinelInterceptor.h */, + RNFB00001A2025011100000002 /* RNFBNullSentinelInterceptor.m */, ); path = RNFBApp; sourceTree = ""; @@ -178,6 +183,7 @@ 2744B99121F46140004F8E3F /* RNFBRCTEventEmitter.m in Sources */, 2744B9A421F48A4F004F8E3F /* RCTConvert+FIROptions.m in Sources */, 2748D8152236426300FC8DC8 /* RNFBJSON.m in Sources */, + RNFB00001A2025011100000001 /* RNFBNullSentinelInterceptor.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/app/ios/RNFBApp/RNFBNullSentinelInterceptor.h b/packages/app/ios/RNFBApp/RNFBNullSentinelInterceptor.h new file mode 100644 index 0000000000..4d2a2d5983 --- /dev/null +++ b/packages/app/ios/RNFBApp/RNFBNullSentinelInterceptor.h @@ -0,0 +1,47 @@ +/** + * 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. + * + */ + +#ifndef RNFBNullSentinelInterceptor_h +#define RNFBNullSentinelInterceptor_h + +#import + +/** + * Intercepts TurboModule conversions to automatically decode null sentinels. + * + * iOS TurboModules strip null values from object properties during serialization. + * See: https://github.com/facebook/react-native/issues/52802 + * The JavaScript side encodes nulls as sentinel objects, + * and this interceptor automatically converts them back to NSNull before they + * reach module implementation methods. + * + * This class uses method swizzling on RCTCxxConvert to intercept all TurboModule + * data conversion methods (JS_*Module_Spec*Data:), decoding sentinels before the + * data reaches the C++ bridging layer and ultimately your module methods. + */ +@interface RNFBNullSentinelInterceptor : NSObject + +/** + * Initializes the null sentinel interceptor. + * This swizzles RCTCxxConvert (TurboModule converter) to automatically decode null sentinels. + * Called automatically when the class is loaded via +load. + */ ++ (void)initialize; + +@end + +#endif diff --git a/packages/app/ios/RNFBApp/RNFBNullSentinelInterceptor.m b/packages/app/ios/RNFBApp/RNFBNullSentinelInterceptor.m new file mode 100644 index 0000000000..3e0b641806 --- /dev/null +++ b/packages/app/ios/RNFBApp/RNFBNullSentinelInterceptor.m @@ -0,0 +1,74 @@ +/** + * 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 "RNFBNullSentinelInterceptor.h" +#import +#import "RNFBSharedUtils.h" + +@implementation RNFBNullSentinelInterceptor + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self swizzleRCTConvertMethods]; + }); +} + ++ (void)swizzleRCTConvertMethods { + // For TurboModules: Swizzle RCTCxxConvert to intercept all conversion methods + Class cxxConvertClass = NSClassFromString(@"RCTCxxConvert"); + if (cxxConvertClass) { + [self swizzleTurboModuleConversions:cxxConvertClass]; + } +} + ++ (void)swizzleTurboModuleConversions:(Class)cxxConvertClass { + // Get all methods from RCTCxxConvert + unsigned int methodCount = 0; + Method *methods = class_copyMethodList(object_getClass(cxxConvertClass), &methodCount); + + for (unsigned int i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + NSString *selectorName = NSStringFromSelector(selector); + + // Intercept TurboModule data conversion methods (they follow pattern: + // JS_NativeRNFBTurbo*_Spec*) + if ([selectorName hasPrefix:@"JS_NativeRNFBTurbo"] && [selectorName containsString:@"_Spec"]) { + // Create a swizzled version using IMP + IMP originalIMP = method_getImplementation(method); + const char *typeEncoding = method_getTypeEncoding(method); + + // Replace with our wrapper that decodes nulls + IMP newIMP = imp_implementationWithBlock(^id(id self, id json) { + // Decode null sentinels before passing to original conversion + id decoded = [RNFBSharedUtils decodeNullSentinels:json]; + + // Call original implementation with decoded data + typedef id (*OriginalFunc)(id, SEL, id); + OriginalFunc originalFunc = (OriginalFunc)originalIMP; + return originalFunc(self, selector, decoded); + }); + + method_setImplementation(method, newIMP); + } + } + + free(methods); +} + +@end diff --git a/packages/app/ios/RNFBApp/RNFBSharedUtils.h b/packages/app/ios/RNFBApp/RNFBSharedUtils.h index 7343c2814f..ebfe513e94 100644 --- a/packages/app/ios/RNFBApp/RNFBSharedUtils.h +++ b/packages/app/ios/RNFBApp/RNFBSharedUtils.h @@ -60,6 +60,8 @@ extern NSString *const DEFAULT_APP_NAME; + (BOOL)getConfigBooleanValue:(NSString *)tag key:(NSString *)key defaultValue:(BOOL)defaultValue; ++ (id)decodeNullSentinels:(id)value; + @end #endif diff --git a/packages/app/ios/RNFBApp/RNFBSharedUtils.m b/packages/app/ios/RNFBApp/RNFBSharedUtils.m index 2975d485f1..3fac5de006 100644 --- a/packages/app/ios/RNFBApp/RNFBSharedUtils.m +++ b/packages/app/ios/RNFBApp/RNFBSharedUtils.m @@ -164,4 +164,131 @@ + (BOOL)getConfigBooleanValue:(NSString *)tag key:(NSString *)key defaultValue:( return enabled; } +/** + * Decodes null sentinel objects back to NSNull values. + * Uses iterative stack-based traversal to avoid stack overflow on deeply nested structures. + * + * This reverses the encoding done on the JavaScript side where null values in object + * properties are replaced with {__rnfbNull: true} sentinel objects to survive iOS + * TurboModule serialization. + * + * Process: + * 1. Detects sentinel objects: dictionaries with single key "__rnfbNull" set to true + * 2. Replaces sentinels with NSNull in object properties and arrays + * 3. Preserves regular NSNull values that were in arrays (never encoded as sentinels) + * 4. Deep processes all nested objects and arrays using a stack-based iteration + * + * @param value - The value to decode (dictionary, array, or primitive) + * @return The decoded value with sentinels replaced by NSNull + */ ++ (id)decodeNullSentinels:(id)value { + // Non-container values are returned as-is + if (![value isKindOfClass:[NSDictionary class]] && ![value isKindOfClass:[NSArray class]]) { + return value; + } + + // Helper to detect the sentinel + BOOL (^isNullSentinel)(NSDictionary *) = ^BOOL(NSDictionary *dict) { + id flag = dict[@"__rnfbNull"]; + return (dict.count == 1 && flag != nil && [flag boolValue]); + }; + + // Root-level sentinel case + if ([value isKindOfClass:[NSDictionary class]] && isNullSentinel((NSDictionary *)value)) { + return [NSNull null]; + } + + id rootOriginal = value; + id rootMutable = nil; + + if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = (NSDictionary *)value; + rootMutable = [NSMutableDictionary dictionaryWithCapacity:dict.count]; + } else { + NSArray *array = (NSArray *)value; + rootMutable = [NSMutableArray arrayWithCapacity:array.count]; + } + + // Stack-based iteration to process nested structures without recursion + // Stack frames: { @"original": container, @"mutable": mutableContainer } + NSMutableArray *stack = [NSMutableArray array]; + [stack addObject:@{@"original" : rootOriginal, @"mutable" : rootMutable}]; + + while (stack.count > 0) { + NSDictionary *frame = [stack lastObject]; + [stack removeLastObject]; + + id original = frame[@"original"]; + id mutable = frame[@"mutable"]; + + if ([original isKindOfClass:[NSDictionary class]]) { + NSDictionary *origDict = (NSDictionary *)original; + NSMutableDictionary *mutDict = (NSMutableDictionary *)mutable; + + for (id key in origDict) { + id child = origDict[key]; + + if ([child isKindOfClass:[NSDictionary class]]) { + NSDictionary *childDict = (NSDictionary *)child; + + if (isNullSentinel(childDict)) { + // Replace sentinel with NSNull + mutDict[key] = [NSNull null]; + } else { + // Process nested dictionary + NSMutableDictionary *childMut = + [NSMutableDictionary dictionaryWithCapacity:childDict.count]; + mutDict[key] = childMut; + [stack addObject:@{@"original" : childDict, @"mutable" : childMut}]; + } + } else if ([child isKindOfClass:[NSArray class]]) { + // Process nested array + NSArray *childArray = (NSArray *)child; + NSMutableArray *childMut = [NSMutableArray arrayWithCapacity:childArray.count]; + mutDict[key] = childMut; + [stack addObject:@{@"original" : childArray, @"mutable" : childMut}]; + } else { + // Preserve primitive values + if (child) { + mutDict[key] = child; + } else { + // NSDictionary can't store nil, and original code wouldn't see nil values either. + } + } + } + } else if ([original isKindOfClass:[NSArray class]]) { + NSArray *origArray = (NSArray *)original; + NSMutableArray *mutArray = (NSMutableArray *)mutable; + + for (id child in origArray) { + if ([child isKindOfClass:[NSDictionary class]]) { + NSDictionary *childDict = (NSDictionary *)child; + + if (isNullSentinel(childDict)) { + // Replace sentinel with NSNull + [mutArray addObject:[NSNull null]]; + } else { + // Process nested dictionary + NSMutableDictionary *childMut = + [NSMutableDictionary dictionaryWithCapacity:childDict.count]; + [mutArray addObject:childMut]; + [stack addObject:@{@"original" : childDict, @"mutable" : childMut}]; + } + } else if ([child isKindOfClass:[NSArray class]]) { + // Process nested array + NSArray *childArray = (NSArray *)child; + NSMutableArray *childMut = [NSMutableArray arrayWithCapacity:childArray.count]; + [mutArray addObject:childMut]; + [stack addObject:@{@"original" : childArray, @"mutable" : childMut}]; + } else { + // Preserve primitive values and NSNull (which never became sentinels in arrays) + [mutArray addObject:child ?: [NSNull null]]; + } + } + } + } + + return rootMutable; +} + @end diff --git a/packages/app/lib/internal/nativeModuleAndroidIos.js b/packages/app/lib/internal/nativeModuleAndroidIos.js index 31a6a2e281..8254e4a91a 100644 --- a/packages/app/lib/internal/nativeModuleAndroidIos.js +++ b/packages/app/lib/internal/nativeModuleAndroidIos.js @@ -1,5 +1,6 @@ /* eslint-disable no-console */ import { NativeModules } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; /** * This is used by Android and iOS to get a native module. @@ -8,7 +9,7 @@ import { NativeModules } from 'react-native'; * @param moduleName */ export function getReactNativeModule(moduleName) { - const nativeModule = NativeModules[moduleName]; + const nativeModule = NativeModules[moduleName] || TurboModuleRegistry.get(moduleName); if (!globalThis.RNFBDebug) { return nativeModule; } diff --git a/packages/app/lib/internal/nullSerialization.js b/packages/app/lib/internal/nullSerialization.js new file mode 100644 index 0000000000..d62e6cd25c --- /dev/null +++ b/packages/app/lib/internal/nullSerialization.js @@ -0,0 +1,145 @@ +/* + * 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. + * + */ + +const NULL_SENTINEL = { __rnfbNull: true }; + +/** + * Replaces null values in object properties with sentinel objects for iOS TurboModule compatibility. + * Uses iterative stack-based traversal to avoid stack overflow on deeply nested structures. + * + * iOS TurboModules strip null values from object properties during serialization, + * so we replace them with sentinel objects that can survive the serialization + * and be detected/restored on the native side. + * + * Note: Null values in arrays are preserved by iOS TurboModules, so we don't + * encode them (but we still process nested objects within arrays). + * + * @param {any} data - The data to encode + * @returns {any} - The encoded data with null object properties replaced by sentinels + */ +export function encodeNullValues(data) { + if (data === null) { + // only null values within objects are encoded + return null; + } + if (typeof data !== 'object') { + return data; + } + + // Prepare root encoded container + let rootEncoded; + const stack = []; + + if (Array.isArray(data)) { + rootEncoded = new Array(data.length); + stack.push({ + type: 'array', + original: data, + encoded: rootEncoded, + index: 0, + }); + } else { + rootEncoded = {}; + stack.push({ + type: 'object', + original: data, + encoded: rootEncoded, + keys: Object.keys(data), + index: 0, + }); + } + + while (stack.length > 0) { + const frame = stack[stack.length - 1]; + + if (frame.type === 'array') { + const { original, encoded } = frame; + + if (frame.index >= original.length) { + // Done with this array + stack.pop(); + continue; + } + + const i = frame.index++; + const item = original[i]; + + if (item === null || typeof item !== 'object') { + // Arrays preserve nulls as null + encoded[i] = item; + } else if (Array.isArray(item)) { + const childEncoded = new Array(item.length); + encoded[i] = childEncoded; + stack.push({ + type: 'array', + original: item, + encoded: childEncoded, + index: 0, + }); + } else { + const childEncoded = {}; + encoded[i] = childEncoded; + stack.push({ + type: 'object', + original: item, + encoded: childEncoded, + keys: Object.keys(item), + index: 0, + }); + } + } else { + // frame.type === 'object' + const { original, encoded, keys } = frame; + + if (frame.index >= keys.length) { + // Done with this object + stack.pop(); + continue; + } + + const key = keys[frame.index++]; + const value = original[key]; + + if (value === null) { + encoded[key] = NULL_SENTINEL; + } else if (typeof value !== 'object') { + encoded[key] = value; + } else if (Array.isArray(value)) { + const childEncoded = new Array(value.length); + encoded[key] = childEncoded; + stack.push({ + type: 'array', + original: value, + encoded: childEncoded, + index: 0, + }); + } else { + const childEncoded = {}; + encoded[key] = childEncoded; + stack.push({ + type: 'object', + original: value, + encoded: childEncoded, + keys: Object.keys(value), + index: 0, + }); + } + } + } + + return rootEncoded; +} diff --git a/packages/app/lib/internal/registry/nativeModule.js b/packages/app/lib/internal/registry/nativeModule.js index 2778b2dd75..43611e46d0 100644 --- a/packages/app/lib/internal/registry/nativeModule.js +++ b/packages/app/lib/internal/registry/nativeModule.js @@ -14,13 +14,13 @@ * limitations under the License. * */ - import { APP_NATIVE_MODULE } from '../constants'; import NativeFirebaseError from '../NativeFirebaseError'; import RNFBNativeEventEmitter from '../RNFBNativeEventEmitter'; import SharedEventEmitter from '../SharedEventEmitter'; import { getReactNativeModule } from '../nativeModule'; import { isAndroid, isIOS } from '../../common'; +import { encodeNullValues } from '../nullSerialization'; const NATIVE_MODULE_REGISTRY = {}; const NATIVE_MODULE_EVENT_SUBSCRIPTIONS = {}; @@ -38,9 +38,14 @@ function nativeModuleKey(module) { * @param argToPrepend * @returns {Function} */ -function nativeModuleMethodWrapped(namespace, method, argToPrepend) { +function nativeModuleMethodWrapped(namespace, method, argToPrepend, isTurboModule) { return (...args) => { - const possiblePromise = method(...[...argToPrepend, ...args]); + // For iOS TurboModules, encode null values in arguments to work around + // the limitation where null values in object properties get stripped during serialization + // See: https://github.com/facebook/react-native/issues/52802 + const processedArgs = isIOS && isTurboModule ? args.map(arg => encodeNullValues(arg)) : args; + const allArgs = [...argToPrepend, ...processedArgs]; + const possiblePromise = method(...allArgs); if (possiblePromise && possiblePromise.then) { const jsStack = new Error().stack; @@ -60,7 +65,7 @@ function nativeModuleMethodWrapped(namespace, method, argToPrepend) { * @param NativeModule * @param argToPrepend */ -function nativeModuleWrapped(namespace, NativeModule, argToPrepend) { +function nativeModuleWrapped(namespace, NativeModule, argToPrepend, isTurboModule) { const native = {}; if (!NativeModule) { return NativeModule; @@ -72,7 +77,12 @@ function nativeModuleWrapped(namespace, NativeModule, argToPrepend) { for (let i = 0, len = properties.length; i < len; i++) { const property = properties[i]; if (typeof NativeModule[property] === 'function') { - native[property] = nativeModuleMethodWrapped(namespace, NativeModule[property], argToPrepend); + native[property] = nativeModuleMethodWrapped( + namespace, + NativeModule[property], + argToPrepend, + isTurboModule, + ); } else { native[property] = NativeModule[property]; } @@ -97,7 +107,9 @@ function initialiseNativeModule(module) { hasMultiAppSupport, hasCustomUrlOrRegionSupport, disablePrependCustomUrlOrRegion, + turboModule, } = config; + const isTurboModule = !!turboModule; const multiModuleRoot = {}; const multiModule = Array.isArray(nativeModuleName); const nativeModuleNames = multiModule ? nativeModuleName : [nativeModuleName]; @@ -125,7 +137,10 @@ function initialiseNativeModule(module) { argToPrepend.push(module._customUrlOrRegion); } - Object.assign(multiModuleRoot, nativeModuleWrapped(namespace, nativeModule, argToPrepend)); + Object.assign( + multiModuleRoot, + nativeModuleWrapped(namespace, nativeModule, argToPrepend, isTurboModule), + ); } if (nativeEvents && nativeEvents.length) { @@ -227,7 +242,13 @@ export function getAppModule() { throw new Error(getMissingModuleHelpText(namespace)); } - NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] = nativeModuleWrapped(namespace, nativeModule, []); + NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] = nativeModuleWrapped( + namespace, + nativeModule, + [], + // TODO: change to true when we use TurboModules for app package + false, + ); return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]; } diff --git a/packages/app/lib/modular/index.d.ts b/packages/app/lib/modular/index.d.ts index 49a8337d82..51f6de4c54 100644 --- a/packages/app/lib/modular/index.d.ts +++ b/packages/app/lib/modular/index.d.ts @@ -1,6 +1,8 @@ import { ReactNativeFirebase } from '..'; -import FirebaseApp = ReactNativeFirebase.FirebaseApp; +type FirebaseApp = ReactNativeFirebase.FirebaseApp & { + functions(regionOrCustomDomain?: string): Functions; +}; import FirebaseAppOptions = ReactNativeFirebase.FirebaseAppOptions; import LogLevelString = ReactNativeFirebase.LogLevelString; import FirebaseAppConfig = ReactNativeFirebase.FirebaseAppConfig; diff --git a/packages/functions/RNFBFunctions.podspec b/packages/functions/RNFBFunctions.podspec index 16c4118ffc..cb04cec6cb 100644 --- a/packages/functions/RNFBFunctions.podspec +++ b/packages/functions/RNFBFunctions.podspec @@ -27,11 +27,20 @@ Pod::Spec.new do |s| s.ios.deployment_target = firebase_ios_target s.macos.deployment_target = firebase_macos_target s.tvos.deployment_target = firebase_tvos_target - s.source_files = 'ios/**/*.{h,m}' + s.source_files = 'ios/**/*.{h,m,mm,cpp}' + s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*' + # Turbo modules require these compiler flags + s.compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1' # React Native dependencies s.dependency 'React-Core' s.dependency 'RNFBApp' + # Turbo module code generated podspecs + s.dependency 'ReactCodegen' + s.dependency 'ReactAppDependencyProvider' + # Turbo modules requires these dependencies + s.dependency 'RCT-Folly' + s.dependency 'React-Fabric' if defined?($FirebaseSDKVersion) Pod::UI.puts "#{s.name}: Using user specified Firebase SDK version '#{$FirebaseSDKVersion}'" diff --git a/packages/functions/__tests__/functions.test.ts b/packages/functions/__tests__/functions.test.ts index 708204b1f7..787ac9a005 100644 --- a/packages/functions/__tests__/functions.test.ts +++ b/packages/functions/__tests__/functions.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, jest } from '@jest/globals'; -import functions, { +import { firebase, getFunctions, connectFunctionsEmulator, @@ -9,12 +9,13 @@ import functions, { HttpsErrorCode, } from '../lib'; +import functions from '../lib/namespaced'; import { createCheckV9Deprecation, - CheckV9DeprecationFunction, + type CheckV9DeprecationFunction, } from '../../app/lib/common/unitTestUtils'; -import { getApp } from '../../app'; +import { getApp } from '@react-native-firebase/app'; // @ts-ignore test import FirebaseModule from '../../app/lib/internal/FirebaseModule'; diff --git a/packages/functions/android/build.gradle b/packages/functions/android/build.gradle index 7bff8b2799..2bafef3bb7 100644 --- a/packages/functions/android/build.gradle +++ b/packages/functions/android/build.gradle @@ -81,6 +81,8 @@ android { sourceSets { main { java.srcDirs = ['src/main/java', 'src/reactnative/java'] + // Exclude generated JNI files from compilation + java.excludes = ['**/generated/jni/**'] } } } diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeFunctionsPackage.java b/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeFunctionsPackage.java new file mode 100644 index 0000000000..aa27916240 --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeFunctionsPackage.java @@ -0,0 +1,44 @@ +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.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +@SuppressWarnings("unused") +public class NativeFunctionsPackage implements ReactPackage { + @Nonnull + @Override + public List createNativeModules(@Nonnull ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new NativeRNFBTurboFunctions(reactContext)); + return modules; + } + + @Nonnull + @Override + public List createViewManagers(@Nonnull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} 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 new file mode 100644 index 0000000000..0e94711c53 --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/NativeRNFBTurboFunctions.java @@ -0,0 +1,139 @@ +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 static io.invertase.firebase.functions.UniversalFirebaseFunctionsModule.CODE_KEY; +import static io.invertase.firebase.functions.UniversalFirebaseFunctionsModule.DATA_KEY; +import static io.invertase.firebase.functions.UniversalFirebaseFunctionsModule.DETAILS_KEY; +import static io.invertase.firebase.functions.UniversalFirebaseFunctionsModule.MSG_KEY; + +import com.facebook.fbreact.specs.NativeRNFBTurboFunctionsSpec; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.google.android.gms.tasks.Task; +import com.google.firebase.functions.FirebaseFunctionsException; +import io.invertase.firebase.common.RCTConvertFirebase; +import io.invertase.firebase.common.UniversalFirebaseModule; +import java.io.IOException; + +public class NativeRNFBTurboFunctions extends NativeRNFBTurboFunctionsSpec { + private static final String SERVICE_NAME = "Functions"; + private final UniversalFirebaseFunctionsModule module; + private final UniversalFirebaseModule universalFirebaseModule; + + public NativeRNFBTurboFunctions(ReactApplicationContext reactContext) { + super(reactContext); + // cannot have multiple inheritance so we make this a property rather than extending it + universalFirebaseModule = new UniversalFirebaseModule(reactContext, SERVICE_NAME); + this.module = new UniversalFirebaseFunctionsModule(reactContext, SERVICE_NAME); + } + + @Override + public void httpsCallable( + String appName, + String region, + String emulatorHost, + double emulatorPort, + String name, + ReadableMap data, + ReadableMap options, + Promise promise) { + + Object callableData = data.toHashMap().get(DATA_KEY); + + // Convert emulatorPort to Integer (null if not using emulator) + Integer port = emulatorHost != null ? (int) emulatorPort : null; + + Task callMethodTask = + module.httpsCallable(appName, region, emulatorHost, port, name, callableData, options); + + // resolve + callMethodTask.addOnSuccessListener( + universalFirebaseModule.getExecutor(), + result -> { + promise.resolve(RCTConvertFirebase.mapPutValue(DATA_KEY, result, Arguments.createMap())); + }); + + // reject + callMethodTask.addOnFailureListener( + universalFirebaseModule.getExecutor(), + exception -> handleFunctionsException(exception, promise)); + } + + @Override + public void httpsCallableFromUrl( + String appName, + String region, + String emulatorHost, + double emulatorPort, + String url, + ReadableMap data, + ReadableMap options, + Promise promise) { + + Object callableData = data.toHashMap().get(DATA_KEY); + + // Convert emulatorPort to Integer (null if not using emulator) + Integer port = emulatorHost != null ? (int) emulatorPort : null; + + Task callMethodTask = + module.httpsCallableFromUrl( + appName, region, emulatorHost, port, url, callableData, options); + + callMethodTask.addOnSuccessListener( + universalFirebaseModule.getExecutor(), + result -> { + promise.resolve(RCTConvertFirebase.mapPutValue(DATA_KEY, result, Arguments.createMap())); + }); + + callMethodTask.addOnFailureListener( + universalFirebaseModule.getExecutor(), + exception -> handleFunctionsException(exception, promise)); + } + + private void handleFunctionsException(Exception exception, Promise promise) { + Object details = null; + String code = "UNKNOWN"; + String message = exception.getMessage(); + WritableMap userInfo = Arguments.createMap(); + + if (exception.getCause() != null) { + FirebaseFunctionsException functionsException = + (FirebaseFunctionsException) exception.getCause(); + details = functionsException.getDetails(); + code = functionsException.getCode().name(); + message = functionsException.getMessage(); + String timeout = FirebaseFunctionsException.Code.DEADLINE_EXCEEDED.name(); + Boolean isTimeout = code.contains(timeout); + + if (functionsException.getCause() instanceof IOException && !isTimeout) { + // return UNAVAILABLE for network io errors, to match iOS + code = FirebaseFunctionsException.Code.UNAVAILABLE.name(); + message = FirebaseFunctionsException.Code.UNAVAILABLE.name(); + } + } + + RCTConvertFirebase.mapPutValue(CODE_KEY, code, userInfo); + RCTConvertFirebase.mapPutValue(MSG_KEY, message, userInfo); + RCTConvertFirebase.mapPutValue(DETAILS_KEY, details, userInfo); + promise.reject(code, message, exception, userInfo); + } +} 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 new file mode 100644 index 0000000000..7784a71ade --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboFunctionsSpec.java @@ -0,0 +1,44 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJavaSpec.js + * + * @nolint + */ + +package com.facebook.fbreact.specs; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class NativeRNFBTurboFunctionsSpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = "NativeRNFBTurboFunctions"; + + public NativeRNFBTurboFunctionsSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public @Nonnull String getName() { + return NAME; + } + + @ReactMethod + @DoNotStrip + public abstract void httpsCallable(String appName, String region, @Nullable String emulatorHost, double emulatorPort, String name, ReadableMap data, ReadableMap options, Promise promise); + + @ReactMethod + @DoNotStrip + public abstract void httpsCallableFromUrl(String appName, String region, @Nullable String emulatorHost, double emulatorPort, String url, ReadableMap data, ReadableMap options, Promise promise); +} diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/CMakeLists.txt b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/CMakeLists.txt new file mode 100644 index 0000000000..b01d2c6044 --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/NativeRNFBTurboFunctions/*.cpp) + +add_library( + react_codegen_NativeRNFBTurboFunctions + OBJECT + ${react_codegen_SRCS} +) + +target_include_directories(react_codegen_NativeRNFBTurboFunctions PUBLIC . react/renderer/components/NativeRNFBTurboFunctions) + +target_link_libraries( + react_codegen_NativeRNFBTurboFunctions + fbjni + jsi + # We need to link different libraries based on whether we are building rncore or not, that's necessary + # because we want to break a circular dependency between react_codegen_rncore and reactnative + reactnative +) + +target_compile_options( + react_codegen_NativeRNFBTurboFunctions + PRIVATE + -DLOG_TAG=\"ReactNative\" + -fexceptions + -frtti + -std=c++20 + -Wall +) 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 new file mode 100644 index 0000000000..2c35a4c6fe --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions-generated.cpp @@ -0,0 +1,38 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJniCpp.js + */ + +#include "NativeRNFBTurboFunctions.h" + +namespace facebook::react { + +static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallable(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, "httpsCallable", "(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_httpsCallableFromUrl(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + 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); +} + +NativeRNFBTurboFunctionsSpecJSI::NativeRNFBTurboFunctionsSpecJSI(const JavaTurboModule::InitParams ¶ms) + : JavaTurboModule(params) { + methodMap_["httpsCallable"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallable}; + methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableFromUrl}; +} + +std::shared_ptr NativeRNFBTurboFunctions_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { + if (moduleName == "NativeRNFBTurboFunctions") { + return std::make_shared(params); + } + return nullptr; +} + +} // namespace facebook::react diff --git a/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions.h b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions.h new file mode 100644 index 0000000000..470e1dda05 --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/NativeRNFBTurboFunctions.h @@ -0,0 +1,31 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJniH.js + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +/** + * JNI C++ class for module 'NativeRNFBTurboFunctions' + */ +class JSI_EXPORT NativeRNFBTurboFunctionsSpecJSI : public JavaTurboModule { +public: + NativeRNFBTurboFunctionsSpecJSI(const JavaTurboModule::InitParams ¶ms); +}; + + +JSI_EXPORT +std::shared_ptr NativeRNFBTurboFunctions_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms); + +} // namespace facebook::react 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 new file mode 100644 index 0000000000..c892ffbf8b --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI-generated.cpp @@ -0,0 +1,46 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleCpp.js + */ + +#include "NativeRNFBTurboFunctionsJSI.h" + +namespace facebook::react { + +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallable(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->httpsCallable( + 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) + ); +} +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableFromUrl(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->httpsCallableFromUrl( + 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) + ); +} + +NativeRNFBTurboFunctionsCxxSpecJSI::NativeRNFBTurboFunctionsCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule("NativeRNFBTurboFunctions", jsInvoker) { + methodMap_["httpsCallable"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallable}; + methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableFromUrl}; +} + + +} // namespace facebook::react 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 new file mode 100644 index 0000000000..2cd49cbaee --- /dev/null +++ b/packages/functions/android/src/main/java/io/invertase/firebase/functions/generated/jni/react/renderer/components/NativeRNFBTurboFunctions/NativeRNFBTurboFunctionsJSI.h @@ -0,0 +1,80 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleH.js + */ + +#pragma once + +#include +#include + +namespace facebook::react { + + + class JSI_EXPORT NativeRNFBTurboFunctionsCxxSpecJSI : public TurboModule { +protected: + NativeRNFBTurboFunctionsCxxSpecJSI(std::shared_ptr jsInvoker); + +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; + +}; + +template +class JSI_EXPORT NativeRNFBTurboFunctionsCxxSpec : public TurboModule { +public: + jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override { + return delegate_.create(rt, propName); + } + + std::vector getPropertyNames(jsi::Runtime& runtime) override { + return delegate_.getPropertyNames(runtime); + } + + static constexpr std::string_view kModuleName = "NativeRNFBTurboFunctions"; + +protected: + NativeRNFBTurboFunctionsCxxSpec(std::shared_ptr jsInvoker) + : TurboModule(std::string{NativeRNFBTurboFunctionsCxxSpec::kModuleName}, jsInvoker), + delegate_(reinterpret_cast(this), jsInvoker) {} + + +private: + class Delegate : public NativeRNFBTurboFunctionsCxxSpecJSI { + public: + Delegate(T *instance, std::shared_ptr jsInvoker) : + NativeRNFBTurboFunctionsCxxSpecJSI(std::move(jsInvoker)), instance_(instance) { + + } + + 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) override { + static_assert( + bridging::getParameterCount(&T::httpsCallable) == 8, + "Expected httpsCallable(...) to have 8 parameters"); + + return bridging::callFromJs( + rt, &T::httpsCallable, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(name), std::move(data), std::move(options)); + } + 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) override { + static_assert( + bridging::getParameterCount(&T::httpsCallableFromUrl) == 8, + "Expected httpsCallableFromUrl(...) to have 8 parameters"); + + 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)); + } + + private: + friend class NativeRNFBTurboFunctionsCxxSpec; + T *instance_; + }; + + Delegate delegate_; +}; + +} // namespace facebook::react diff --git a/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.h b/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.h index b608e558e3..198f0f30c6 100644 --- a/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.h +++ b/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.h @@ -16,9 +16,8 @@ */ #import +#import "NativeRNFBTurboFunctions.h" -#import - -@interface RNFBFunctionsModule : NSObject +@interface RNFBFunctionsModule : NSObject @end diff --git a/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.m b/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.mm similarity index 73% rename from packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.m rename to packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.mm index 1f4be2b3dc..6f09b7318e 100644 --- a/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.m +++ b/packages/functions/ios/RNFBFunctions/RNFBFunctionsModule.mm @@ -18,55 +18,67 @@ #import #import +#import "NativeRNFBTurboFunctions.h" +#import "RNFBApp/RCTConvert+FIRApp.h" #import "RNFBApp/RNFBSharedUtils.h" #import "RNFBFunctionsModule.h" +@interface RNFBFunctionsModule () +@end + @implementation RNFBFunctionsModule #pragma mark - #pragma mark Module Setup -RCT_EXPORT_MODULE(); - +RCT_EXPORT_MODULE(NativeRNFBTurboFunctions) #pragma mark - #pragma mark Firebase Functions Methods -RCT_EXPORT_METHOD(httpsCallable - : (FIRApp *)firebaseApp customUrlOrRegion - : (NSString *)customUrlOrRegion host - : (NSString *)host port - : (NSNumber *_Nonnull)port name - : (NSString *)name wrapper - : (NSDictionary *)wrapper options - : (NSDictionary *)options resolver - : (RCTPromiseResolveBlock)resolve rejecter - : (RCTPromiseRejectBlock)reject) { +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} + +- (void)httpsCallable:(NSString *)appName + region:(NSString *)customUrlOrRegion + emulatorHost:(NSString *_Nullable)emulatorHost + emulatorPort:(double)emulatorPort + name:(NSString *)name + data:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableData &)data + options:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableOptions &)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { NSURL *url = [NSURL URLWithString:customUrlOrRegion]; + FIRApp *firebaseApp = [RCTConvert firAppFromString:appName]; + FIRFunctions *functions = (url && url.scheme && url.host) ? [FIRFunctions functionsForApp:firebaseApp customDomain:customUrlOrRegion] : [FIRFunctions functionsForApp:firebaseApp region:customUrlOrRegion]; - if (host != nil) { - [functions useEmulatorWithHost:host port:[port intValue]]; - } - - FIRHTTPSCallable *callable = [functions HTTPSCallableWithName:name]; - - if (options[@"timeout"]) { - callable.timeoutInterval = [options[@"timeout"] doubleValue]; - } + id callableData = data.data(); // In reality, this value is always null, because we always call it with null data // on the javascript side for some reason. Check for that case (which should be 100% of the time) // and set it to an `NSNull` (versus the `Optional` Swift will see from `valueForKey` so that // FirebaseFunctions serializer won't have a validation failure for an unknown type. - id data = [wrapper valueForKey:@"data"]; - NSLog(@"RNFBFUNCTIONS pulled data from 'wrapper', has type %@", [data class]); - if (data == nil) { - data = [NSNull null]; + if (callableData == nil) { + callableData = [NSNull null]; + } + + std::optional timeout = options.timeout(); + + if (emulatorHost != nil) { + [functions useEmulatorWithHost:emulatorHost port:(int)emulatorPort]; } - [callable callWithObject:data + FIRHTTPSCallable *callable = [functions HTTPSCallableWithName:name]; + + if (timeout.has_value()) { + callable.timeoutInterval = timeout.value(); + } + + [callable callWithObject:callableData completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { if (error) { NSObject *details = [NSNull null]; @@ -90,44 +102,49 @@ @implementation RNFBFunctionsModule }]; } -RCT_EXPORT_METHOD(httpsCallableFromUrl - : (FIRApp *)firebaseApp customUrlOrRegion - : (NSString *)customUrlOrRegion host - : (NSString *)host port - : (NSNumber *_Nonnull)port url - : (NSString *)url wrapper - : (NSDictionary *)wrapper options - : (NSDictionary *)options resolver - : (RCTPromiseResolveBlock)resolve rejecter - : (RCTPromiseRejectBlock)reject) { +- (void)httpsCallableFromUrl:(NSString *)appName + region:(NSString *)customUrlOrRegion + emulatorHost:(NSString *_Nullable)emulatorHost + emulatorPort:(double)emulatorPort + url:(NSString *)url + data:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableFromUrlData &)data + options: + (JS::NativeRNFBTurboFunctions::SpecHttpsCallableFromUrlOptions &)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { NSURL *customUrl = [NSURL URLWithString:customUrlOrRegion]; + FIRApp *firebaseApp = [RCTConvert firAppFromString:appName]; + FIRFunctions *functions = (customUrl && customUrl.scheme && customUrl.host) ? [FIRFunctions functionsForApp:firebaseApp customDomain:customUrlOrRegion] : [FIRFunctions functionsForApp:firebaseApp region:customUrlOrRegion]; - if (host != nil) { - [functions useEmulatorWithHost:host port:[port intValue]]; + id callableData = data.data(); + + // In reality, this value is always null, because we always call it with null data + // on the javascript side for some reason. Check for that case (which should be 100% of the time) + // and set it to an `NSNull` (versus the `Optional` Swift will see from `valueForKey` so that + // FirebaseFunctions serializer won't have a validation failure for an unknown type. + if (callableData == nil) { + callableData = [NSNull null]; + } + + std::optional timeout = options.timeout(); + + if (emulatorHost != nil) { + [functions useEmulatorWithHost:emulatorHost port:(int)emulatorPort]; } NSURL *functionUrl = [NSURL URLWithString:url]; FIRHTTPSCallable *callable = [functions HTTPSCallableWithURL:functionUrl]; - if (options[@"timeout"]) { - callable.timeoutInterval = [options[@"timeout"] doubleValue]; - } - - // In reality, this value is always null, because we always call it with null data - // on the javascript side for some reason. Check for that case (which should be 100% of the time) - // and set it to an `NSNull` (versus the `Optional` Swift will see from `valueForKey` so that - // FirebaseFunctions serializer won't have a validation failure for an unknown type. - id data = [wrapper valueForKey:@"data"]; - if (data == nil) { - data = [NSNull null]; + if (timeout.has_value()) { + callable.timeoutInterval = timeout.value(); } - [callable callWithObject:data + [callable callWithObject:callableData completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { if (error) { NSObject *details = [NSNull null]; diff --git a/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions-generated.mm b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions-generated.mm new file mode 100644 index 0000000000..7fff3b0c74 --- /dev/null +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions-generated.mm @@ -0,0 +1,71 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleObjCpp + * + * We create an umbrella header (and corresponding implementation) here since + * Cxx compilation in BUCK has a limitation: source-code producing genrule()s + * must have a single output. More files => more genrule()s => slower builds. + */ + +#import "NativeRNFBTurboFunctions.h" + + +@implementation NativeRNFBTurboFunctionsSpecBase + + +- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper +{ + _eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback); +} +@end + +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableData:(id)json +{ + return facebook::react::managedPointer(json); +} +@end +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableOptions:(id)json +{ + return facebook::react::managedPointer(json); +} +@end +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlData:(id)json +{ + return facebook::react::managedPointer(json); +} +@end +@implementation RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions:(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) { + return static_cast(turboModule).invokeObjCMethod(rt, PromiseKind, "httpsCallable", @selector(httpsCallable:region:emulatorHost:emulatorPort:name:data:options:resolve:reject:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableFromUrl(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, PromiseKind, "httpsCallableFromUrl", @selector(httpsCallableFromUrl:region:emulatorHost:emulatorPort:url:data:options:resolve:reject:), args, count); + } + + NativeRNFBTurboFunctionsSpecJSI::NativeRNFBTurboFunctionsSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + + methodMap_["httpsCallable"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallable}; + setMethodArgConversionSelector(@"httpsCallable", 5, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableData:"); + setMethodArgConversionSelector(@"httpsCallable", 6, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableOptions:"); + + methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsSpecJSI_httpsCallableFromUrl}; + setMethodArgConversionSelector(@"httpsCallableFromUrl", 5, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlData:"); + setMethodArgConversionSelector(@"httpsCallableFromUrl", 6, @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions:"); + } +} // namespace facebook::react diff --git a/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions.h b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions.h new file mode 100644 index 0000000000..f7e34a1777 --- /dev/null +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctions/NativeRNFBTurboFunctions.h @@ -0,0 +1,157 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleObjCpp + * + * We create an umbrella header (and corresponding implementation) here since + * Cxx compilation in BUCK has a limitation: source-code producing genrule()s + * must have a single output. More files => more genrule()s => slower builds. + */ + +#ifndef __cplusplus +#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm. +#endif + +// Avoid multiple includes of NativeRNFBTurboFunctions symbols +#ifndef NativeRNFBTurboFunctions_H +#define NativeRNFBTurboFunctions_H + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +NS_ASSUME_NONNULL_BEGIN +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableData { + id data() const; + + SpecHttpsCallableData(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableData:(id)json; +@end +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableOptions { + std::optional timeout() const; + + SpecHttpsCallableOptions(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableOptions:(id)json; +@end +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableFromUrlData { + id data() const; + + SpecHttpsCallableFromUrlData(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlData) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlData:(id)json; +@end +namespace JS { + namespace NativeRNFBTurboFunctions { + struct SpecHttpsCallableFromUrlOptions { + std::optional timeout() const; + + SpecHttpsCallableFromUrlOptions(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions) ++ (RCTManagedPointer *)JS_NativeRNFBTurboFunctions_SpecHttpsCallableFromUrlOptions:(id)json; +@end +@protocol NativeRNFBTurboFunctionsSpec + +- (void)httpsCallable:(NSString *)appName + region:(NSString *)region + emulatorHost:(NSString * _Nullable)emulatorHost + emulatorPort:(double)emulatorPort + name:(NSString *)name + data:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableData &)data + options:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableOptions &)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; +- (void)httpsCallableFromUrl:(NSString *)appName + region:(NSString *)region + emulatorHost:(NSString * _Nullable)emulatorHost + emulatorPort:(double)emulatorPort + url:(NSString *)url + data:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableFromUrlData &)data + options:(JS::NativeRNFBTurboFunctions::SpecHttpsCallableFromUrlOptions &)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; + +@end + +@interface NativeRNFBTurboFunctionsSpecBase : NSObject { +@protected +facebook::react::EventEmitterCallback _eventEmitterCallback; +} +- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper; + + +@end + +namespace facebook::react { + /** + * ObjC++ class for module 'NativeRNFBTurboFunctions' + */ + class JSI_EXPORT NativeRNFBTurboFunctionsSpecJSI : public ObjCTurboModule { + public: + NativeRNFBTurboFunctionsSpecJSI(const ObjCTurboModule::InitParams ¶ms); + }; +} // namespace facebook::react +inline id JS::NativeRNFBTurboFunctions::SpecHttpsCallableData::data() const +{ + id const p = _v[@"data"]; + return p; +} +inline std::optional JS::NativeRNFBTurboFunctions::SpecHttpsCallableOptions::timeout() const +{ + id const p = _v[@"timeout"]; + return RCTBridgingToOptionalDouble(p); +} +inline id JS::NativeRNFBTurboFunctions::SpecHttpsCallableFromUrlData::data() const +{ + id const p = _v[@"data"]; + return p; +} +inline std::optional JS::NativeRNFBTurboFunctions::SpecHttpsCallableFromUrlOptions::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 new file mode 100644 index 0000000000..c892ffbf8b --- /dev/null +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI-generated.cpp @@ -0,0 +1,46 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleCpp.js + */ + +#include "NativeRNFBTurboFunctionsJSI.h" + +namespace facebook::react { + +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallable(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->httpsCallable( + 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) + ); +} +static jsi::Value __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableFromUrl(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->httpsCallableFromUrl( + 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) + ); +} + +NativeRNFBTurboFunctionsCxxSpecJSI::NativeRNFBTurboFunctionsCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule("NativeRNFBTurboFunctions", jsInvoker) { + methodMap_["httpsCallable"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallable}; + methodMap_["httpsCallableFromUrl"] = MethodMetadata {7, __hostFunction_NativeRNFBTurboFunctionsCxxSpecJSI_httpsCallableFromUrl}; +} + + +} // namespace facebook::react diff --git a/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI.h b/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI.h new file mode 100644 index 0000000000..2cd49cbaee --- /dev/null +++ b/packages/functions/ios/generated/NativeRNFBTurboFunctionsJSI.h @@ -0,0 +1,80 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleH.js + */ + +#pragma once + +#include +#include + +namespace facebook::react { + + + class JSI_EXPORT NativeRNFBTurboFunctionsCxxSpecJSI : public TurboModule { +protected: + NativeRNFBTurboFunctionsCxxSpecJSI(std::shared_ptr jsInvoker); + +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; + +}; + +template +class JSI_EXPORT NativeRNFBTurboFunctionsCxxSpec : public TurboModule { +public: + jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override { + return delegate_.create(rt, propName); + } + + std::vector getPropertyNames(jsi::Runtime& runtime) override { + return delegate_.getPropertyNames(runtime); + } + + static constexpr std::string_view kModuleName = "NativeRNFBTurboFunctions"; + +protected: + NativeRNFBTurboFunctionsCxxSpec(std::shared_ptr jsInvoker) + : TurboModule(std::string{NativeRNFBTurboFunctionsCxxSpec::kModuleName}, jsInvoker), + delegate_(reinterpret_cast(this), jsInvoker) {} + + +private: + class Delegate : public NativeRNFBTurboFunctionsCxxSpecJSI { + public: + Delegate(T *instance, std::shared_ptr jsInvoker) : + NativeRNFBTurboFunctionsCxxSpecJSI(std::move(jsInvoker)), instance_(instance) { + + } + + 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) override { + static_assert( + bridging::getParameterCount(&T::httpsCallable) == 8, + "Expected httpsCallable(...) to have 8 parameters"); + + return bridging::callFromJs( + rt, &T::httpsCallable, jsInvoker_, instance_, std::move(appName), std::move(region), std::move(emulatorHost), std::move(emulatorPort), std::move(name), std::move(data), std::move(options)); + } + 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) override { + static_assert( + bridging::getParameterCount(&T::httpsCallableFromUrl) == 8, + "Expected httpsCallableFromUrl(...) to have 8 parameters"); + + 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)); + } + + private: + friend class NativeRNFBTurboFunctionsCxxSpec; + T *instance_; + }; + + Delegate delegate_; +}; + +} // namespace facebook::react diff --git a/packages/functions/ios/generated/RCTAppDependencyProvider.h b/packages/functions/ios/generated/RCTAppDependencyProvider.h new file mode 100644 index 0000000000..5a1502d16f --- /dev/null +++ b/packages/functions/ios/generated/RCTAppDependencyProvider.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + +#import + +#if __has_include() +#import +#elif __has_include() +#import +#else +#import "RCTDependencyProvider.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTAppDependencyProvider : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/functions/ios/generated/RCTAppDependencyProvider.mm b/packages/functions/ios/generated/RCTAppDependencyProvider.mm new file mode 100644 index 0000000000..b76c468c8a --- /dev/null +++ b/packages/functions/ios/generated/RCTAppDependencyProvider.mm @@ -0,0 +1,40 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTAppDependencyProvider.h" +#import +#import +#import +#import + +@implementation RCTAppDependencyProvider + +- (nonnull NSArray *)URLRequestHandlerClassNames { + return RCTModulesConformingToProtocolsProvider.URLRequestHandlerClassNames; +} + +- (nonnull NSArray *)imageDataDecoderClassNames { + return RCTModulesConformingToProtocolsProvider.imageDataDecoderClassNames; +} + +- (nonnull NSArray *)imageURLLoaderClassNames { + return RCTModulesConformingToProtocolsProvider.imageURLLoaderClassNames; +} + +- (nonnull NSArray *)unstableModulesRequiringMainQueueSetup { + return RCTUnstableModulesRequiringMainQueueSetupProvider.modules; +} + +- (nonnull NSDictionary> *)thirdPartyFabricComponents { + return RCTThirdPartyComponentsProvider.thirdPartyFabricComponents; +} + +- (nonnull NSDictionary> *)moduleProviders { + return RCTModuleProviders.moduleProviders; +} + +@end diff --git a/packages/functions/ios/generated/RCTModuleProviders.h b/packages/functions/ios/generated/RCTModuleProviders.h new file mode 100644 index 0000000000..aff637c692 --- /dev/null +++ b/packages/functions/ios/generated/RCTModuleProviders.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@protocol RCTModuleProvider; + +@interface RCTModuleProviders: NSObject + ++ (NSDictionary> *)moduleProviders; + +@end diff --git a/packages/functions/ios/generated/RCTModuleProviders.mm b/packages/functions/ios/generated/RCTModuleProviders.mm new file mode 100644 index 0000000000..a994b27d57 --- /dev/null +++ b/packages/functions/ios/generated/RCTModuleProviders.mm @@ -0,0 +1,51 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import "RCTModuleProviders.h" +#import +#import + +@implementation RCTModuleProviders + ++ (NSDictionary> *)moduleProviders +{ + static NSDictionary> *providers = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + NSDictionary * moduleMapping = @{ + @"NativeRNFBTurboFunctions": @"RNFBFunctionsModule", // @react-native-firebase/functions + }; + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:moduleMapping.count]; + + for (NSString *key in moduleMapping) { + NSString * moduleProviderName = moduleMapping[key]; + Class klass = NSClassFromString(moduleProviderName); + if (!klass) { + RCTLogError(@"Module provider %@ cannot be found in the runtime", moduleProviderName); + continue; + } + + id instance = [klass new]; + if (![instance respondsToSelector:@selector(getTurboModule:)]) { + RCTLogError(@"Module provider %@ does not conform to RCTModuleProvider", moduleProviderName); + continue; + } + + [dict setObject:instance forKey:key]; + } + + providers = dict; + }); + + return providers; +} + +@end diff --git a/packages/functions/ios/generated/RCTModulesConformingToProtocolsProvider.h b/packages/functions/ios/generated/RCTModulesConformingToProtocolsProvider.h new file mode 100644 index 0000000000..10eb848917 --- /dev/null +++ b/packages/functions/ios/generated/RCTModulesConformingToProtocolsProvider.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@interface RCTModulesConformingToProtocolsProvider: NSObject + ++(NSArray *)imageURLLoaderClassNames; + ++(NSArray *)imageDataDecoderClassNames; + ++(NSArray *)URLRequestHandlerClassNames; + +@end diff --git a/packages/functions/ios/generated/RCTModulesConformingToProtocolsProvider.mm b/packages/functions/ios/generated/RCTModulesConformingToProtocolsProvider.mm new file mode 100644 index 0000000000..a5ff82810e --- /dev/null +++ b/packages/functions/ios/generated/RCTModulesConformingToProtocolsProvider.mm @@ -0,0 +1,54 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTModulesConformingToProtocolsProvider.h" + +@implementation RCTModulesConformingToProtocolsProvider + ++(NSArray *)imageURLLoaderClassNames +{ + static NSArray *classNames = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + classNames = @[ + + ]; + }); + + return classNames; +} + ++(NSArray *)imageDataDecoderClassNames +{ + static NSArray *classNames = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + classNames = @[ + + ]; + }); + + return classNames; +} + ++(NSArray *)URLRequestHandlerClassNames +{ + static NSArray *classNames = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + classNames = @[ + + ]; + }); + + return classNames; +} + +@end diff --git a/packages/functions/ios/generated/RCTThirdPartyComponentsProvider.h b/packages/functions/ios/generated/RCTThirdPartyComponentsProvider.h new file mode 100644 index 0000000000..ab1a249de3 --- /dev/null +++ b/packages/functions/ios/generated/RCTThirdPartyComponentsProvider.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@protocol RCTComponentViewProtocol; + +@interface RCTThirdPartyComponentsProvider: NSObject + ++ (NSDictionary> *)thirdPartyFabricComponents; + +@end diff --git a/packages/functions/ios/generated/RCTThirdPartyComponentsProvider.mm b/packages/functions/ios/generated/RCTThirdPartyComponentsProvider.mm new file mode 100644 index 0000000000..4e05532c39 --- /dev/null +++ b/packages/functions/ios/generated/RCTThirdPartyComponentsProvider.mm @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + +#import + +#import "RCTThirdPartyComponentsProvider.h" +#import + +@implementation RCTThirdPartyComponentsProvider + ++ (NSDictionary> *)thirdPartyFabricComponents +{ + static NSDictionary> *thirdPartyComponents = nil; + static dispatch_once_t nativeComponentsToken; + + dispatch_once(&nativeComponentsToken, ^{ + thirdPartyComponents = @{ + + }; + }); + + return thirdPartyComponents; +} + +@end diff --git a/packages/functions/ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.h b/packages/functions/ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.h new file mode 100644 index 0000000000..114d32253a --- /dev/null +++ b/packages/functions/ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@interface RCTUnstableModulesRequiringMainQueueSetupProvider: NSObject + ++(NSArray *)modules; + +@end diff --git a/packages/functions/ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.mm b/packages/functions/ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.mm new file mode 100644 index 0000000000..9cc59ed7e3 --- /dev/null +++ b/packages/functions/ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.mm @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTUnstableModulesRequiringMainQueueSetupProvider.h" + +@implementation RCTUnstableModulesRequiringMainQueueSetupProvider + ++(NSArray *)modules +{ + return @[ + + ]; +} + +@end diff --git a/packages/functions/ios/generated/ReactAppDependencyProvider.podspec b/packages/functions/ios/generated/ReactAppDependencyProvider.podspec new file mode 100644 index 0000000000..954a2eb6ed --- /dev/null +++ b/packages/functions/ios/generated/ReactAppDependencyProvider.podspec @@ -0,0 +1,34 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +version = "0.80.2" +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +Pod::Spec.new do |s| + s.name = "ReactAppDependencyProvider" + s.version = version + s.summary = "The third party dependency provider for the app" + s.homepage = "https://reactnative.dev/" + s.documentation_url = "https://reactnative.dev/" + s.license = "MIT" + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = min_supported_versions + s.source = source + s.source_files = "**/RCTAppDependencyProvider.{h,mm}" + + # This guard prevent to install the dependencies when we run `pod install` in the old architecture. + s.pod_target_xcconfig = { + "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), + "DEFINES_MODULE" => "YES" + } + + s.dependency "ReactCodegen" +end diff --git a/packages/functions/ios/generated/ReactCodegen.podspec b/packages/functions/ios/generated/ReactCodegen.podspec new file mode 100644 index 0000000000..34d0623ad2 --- /dev/null +++ b/packages/functions/ios/generated/ReactCodegen.podspec @@ -0,0 +1,106 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +version = "0.80.2" +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +use_frameworks = ENV['USE_FRAMEWORKS'] != nil +folly_compiler_flags = Helpers::Constants.folly_config[:compiler_flags] +boost_compiler_flags = Helpers::Constants.boost_config[:compiler_flags] + +header_search_paths = [ + "\"$(PODS_ROOT)/ReactNativeDependencies\"", + "\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"", + "\"$(PODS_ROOT)/Headers/Private/React-Fabric\"", + "\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"", + "\"$(PODS_ROOT)/Headers/Private/Yoga\"", + "\"$(PODS_TARGET_SRCROOT)\"", +] +framework_search_paths = [] + +if use_frameworks + ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"]) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-rendererdebug", "React_rendererdebug", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-featureflags", "React_featureflags", [])) + .each { |search_path| + header_search_paths << "\"#{search_path}\"" + } +end + +Pod::Spec.new do |s| + s.name = "ReactCodegen" + s.version = version + s.summary = 'Temp pod for generated files for React Native' + s.homepage = 'https://facebook.com/' + s.license = 'Unlicense' + s.authors = 'Facebook' + s.compiler_flags = "#{folly_compiler_flags} #{boost_compiler_flags} -Wno-nullability-completeness -std=c++20" + s.source = { :git => '' } + s.header_mappings_dir = './' + s.platforms = min_supported_versions + s.source_files = "**/*.{h,mm,cpp}" + s.exclude_files = "RCTAppDependencyProvider.{h,mm}" # these files are generated in the same codegen path but needs to belong to a different pod + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), + "FRAMEWORK_SEARCH_PATHS" => framework_search_paths, + "OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}" + } + + s.dependency "React-jsiexecutor" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "React-Core" + s.dependency "React-jsi" + s.dependency "ReactCommon/turbomodule/bridging" + s.dependency "ReactCommon/turbomodule/core" + s.dependency "React-NativeModulesApple" + s.dependency 'React-graphics' + s.dependency 'React-rendererdebug' + s.dependency 'React-Fabric' + s.dependency 'React-FabricImage' + s.dependency 'React-debug' + s.dependency 'React-utils' + s.dependency 'React-featureflags' + s.dependency 'React-RCTAppDelegate' + + depend_on_js_engine(s) + add_rn_third_party_dependencies(s) + + s.script_phases = { + 'name' => 'Generate Specs', + 'execution_position' => :before_compile, + 'input_files' => ["${PODS_ROOT}/../../specs/NativeRNFBTurboFunctions.ts"], + 'show_env_vars_in_log' => true, + 'output_files' => ["${DERIVED_FILE_DIR}/react-codegen.log"], + 'script': <<-SCRIPT +pushd "$PODS_ROOT/../" > /dev/null +RCT_SCRIPT_POD_INSTALLATION_ROOT=$(pwd) +popd >/dev/null + +export RCT_SCRIPT_RN_DIR="$RCT_SCRIPT_POD_INSTALLATION_ROOT/../../node_modules/react-native" +export RCT_SCRIPT_APP_PATH="$RCT_SCRIPT_POD_INSTALLATION_ROOT/../.." +export RCT_SCRIPT_OUTPUT_DIR="$RCT_SCRIPT_POD_INSTALLATION_ROOT" +export RCT_SCRIPT_TYPE="withCodegenDiscovery" + +SCRIPT_PHASES_SCRIPT="$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh" +WITH_ENVIRONMENT="$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh" +/bin/sh -c "$WITH_ENVIRONMENT $SCRIPT_PHASES_SCRIPT" +SCRIPT + } + +end diff --git a/packages/functions/lib/HttpsError.js b/packages/functions/lib/HttpsError.ts similarity index 67% rename from packages/functions/lib/HttpsError.js rename to packages/functions/lib/HttpsError.ts index 3c4a6e59d0..5f5cd392dd 100644 --- a/packages/functions/lib/HttpsError.js +++ b/packages/functions/lib/HttpsError.ts @@ -17,8 +17,26 @@ import { NativeFirebaseError } from '@react-native-firebase/app/lib/internal'; -export default class HttpsError extends Error { - constructor(code, message, details, nativeErrorInstance) { +export interface NativeError { + userInfo?: { + code?: string; + message?: string; + details?: Record; + }; + jsStack?: string; + message?: string; +} +export class HttpsError extends Error { + readonly code!: string; + readonly details!: Record | null; + readonly message!: string; + + constructor( + code: string, + message?: string, + details?: Record | null, + nativeErrorInstance?: NativeError, + ) { super(message); Object.defineProperty(this, 'code', { @@ -37,8 +55,8 @@ export default class HttpsError extends Error { }); this.stack = NativeFirebaseError.getStackWithMessage( - `Error: ${this.message}`, - nativeErrorInstance.jsStack, + `Error: ${message}`, + nativeErrorInstance?.jsStack, ); } } diff --git a/packages/functions/lib/index.d.ts b/packages/functions/lib/index.d.ts deleted file mode 100644 index 7475e7ffdd..0000000000 --- a/packages/functions/lib/index.d.ts +++ /dev/null @@ -1,448 +0,0 @@ -/* - * 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 { ReactNativeFirebase } from '@react-native-firebase/app'; - -/** - * Firebase Cloud Functions package for React Native. - * - * #### Example 1 - * - * Access the firebase export from the `functions` package: - * - * ```js - * import { firebase } from '@react-native-firebase/functions'; - * - * // firebase.functions().X - * ``` - * - * #### Example 2 - * - * Using the default export from the `functions` package: - * - * ```js - * import functions from '@react-native-firebase/functions'; - * - * // functions().X - * ``` - * - * #### Example 3 - * - * Using the default export from the `app` package: - * - * ```js - * import firebase from '@react-native-firebase/app'; - * import '@react-native-firebase/functions'; - * - * // firebase.functions().X - * ``` - * - * @firebase functions - */ -export namespace FirebaseFunctionsTypes { - /** - * The set of Firebase Functions status codes. - * - * The codes are the same at the ones exposed by [gRPC](https://github.com/grpc/grpc/blob/master/doc/statuscodes.md). - * - * Possible values: - * - `cancelled`: The operation was cancelled (typically by the caller). - * - `unknown`: Unknown error or an error from a different error domain. - * - `invalid-argument`: Client specified an invalid argument. Note that this - * differs from `failed-precondition`. `invalid-argument` indicates - * arguments that are problematic regardless of the state of the system - * (e.g. an invalid field name). - * - `deadline-exceeded`: Deadline expired before operation could complete. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For example, - * a successful response from a server could have been delayed long enough - * for the deadline to expire. - * - `not-found`: Some requested document was not found. - * - `already-exists`: Some document that we attempted to create already - * exists. - * - `permission-denied`: The caller does not have permission to execute the - * specified operation. - * - `resource-exhausted`: Some resource has been exhausted, perhaps a - * per-user quota, or perhaps the entire file system is out of space. - * - `failed-precondition`: Operation was rejected because the system is not - * in a state required for the operation's execution. - * - `aborted`: The operation was aborted, typically due to a concurrency - * issue like transaction aborts, etc. - * - `out-of-range`: Operation was attempted past the valid range. - * - `unimplemented`: Operation is not implemented or not supported/enabled. - * - `internal`: Internal errors. Means some invariants expected by - * underlying system has been broken. If you see one of these errors, - * something is very broken. - * - `unavailable`: The service is currently unavailable. This is most likely - * a transient condition and may be corrected by retrying with a backoff. - * - `data-loss`: Unrecoverable data loss or corruption. - * - `unauthenticated`: The request does not have valid authentication - * credentials for the operation. - */ - import FirebaseModule = ReactNativeFirebase.FirebaseModule; - - export type FunctionsErrorCode = - | 'ok' - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; - - /** - * An HttpsCallableResult wraps a single result from a function call. - */ - export interface HttpsCallableResult { - readonly data: ResponseData; - } - - /** - * An HttpsCallable is a reference to a "callable" http trigger in - * Google Cloud Functions. - * - * #### Example - * - * ```js - * // Create an HttpsCallable reference - * const reference = firebase.functions().httpsCallable('order'); - * - * try { - * const response = await reference({ - * id: '12345', - * }); - * } catch (e) { - * console.error(e); - * } - * ``` - */ - export interface HttpsCallable { - (data?: RequestData | null): Promise>; - } - - /** - * An interface for metadata about how calls should be executed. An instance of HttpsCallableOptions can be passed as the second argument to `firebase.functions().httpsCallable(name, httpsCallableOptions)`. - **/ - export interface HttpsCallableOptions { - /** - * The timeout property is the time in milliseconds after which to cancel if there is no response. Default is 7000 milliseconds (7 seconds). - * - * #### Example - * - *```js - * // The below will wait 7 seconds for a response from the cloud function before an error is thrown. - * try { - * const instance = firebase.functions().httpsCallable('order', { timeout: 7000 }); - * const response = await instance({ - * id: '12345', - * }); - * } catch (e) { - * console.log(e); - * } - * ``` - */ - timeout?: number; - } - - /** - * An HttpsError wraps a single error from a function call. - * - * #### Example - * - * ```js - * try { - * await firebase.functions().httpsCallable('order')(); - * } catch (httpsError) { - * console.log('Message', httpsError.message); - * - * // Check code - * if (httpsError.code === firebase.functions.HttpsErrorCode.NOT_FOUND) { - * console.error('Functions endpoint "order" not found'); - * } - * } - * ``` - */ - export interface HttpsError extends Error { - /** - * A standard error code that will be returned to the client. This also - * determines the HTTP status code of the response, as defined in code.proto. - * - * #### Example - * - * ```js - * try { - * await firebase.functions().httpsCallable('order')(); - * } catch (httpsError) { - * console.error(httpsError.code); - * } - * ``` - */ - readonly code: FunctionsErrorCode; - /** - * Extra data to be converted to JSON and included in the error response. - * - * ```js - * try { - * await firebase.functions().httpsCallable('order')(); - * } catch (httpsError) { - * if (httpsError.details) { - * console.error(httpsError.details); - * } - * } - * ``` - */ - readonly details?: any; - } - - /** - * The HttpsErrorCode interface provides access to all FunctionsErrorCode - * type aliases. - * - * #### Example - * - * ```js - * try { - * await firebase.functions().httpsCallable('order')(); - * } catch (httpsError) { - * switch(httpsError.code) { - * case firebase.functions.HttpsErrorCode.NOT_FOUND: - * console.error('Functions endpoint not found'); - * break; - * case firebase.functions.HttpsErrorCode.CANCELLED: - * console.error('The operation was cancelled'); - * break; - * default: - * console.error('An error occurred'); - * break; - * } - * } - * ``` - */ - export interface HttpsErrorCode { - OK: 'ok'; - CANCELLED: 'cancelled'; - UNKNOWN: 'unknown'; - INVALID_ARGUMENT: 'invalid-argument'; - DEADLINE_EXCEEDED: 'deadline-exceeded'; - NOT_FOUND: 'not-found'; - ALREADY_EXISTS: 'already-exists'; - PERMISSION_DENIED: 'permission-denied'; - UNAUTHENTICATED: 'unauthenticated'; - RESOURCE_EXHAUSTED: 'resource-exhausted'; - FAILED_PRECONDITION: 'failed-precondition'; - ABORTED: 'aborted'; - OUT_OF_RANGE: 'out-of-range'; - UNIMPLEMENTED: 'unimplemented'; - INTERNAL: 'internal'; - UNAVAILABLE: 'unavailable'; - DATA_LOSS: 'data-loss'; - } - - /** - * firebase.functions.X - */ - export interface Statics { - /** - * Uppercase + underscored variables of {@link functions.FunctionsErrorCode} - * - * #### Example - * - * ```js - * firebase.functions.HttpsErrorCode.OK; - * firebase.functions.HttpsErrorCode.NOT_FOUND; - * ``` - */ - HttpsErrorCode: HttpsErrorCode; - } - - /** - * The Firebase Cloud Functions service is available for the default app, a given app or a specified region. - * - * > The default functions region for all apps is `us-central1`. - * - * #### Example 1 - * - * Get the functions instance for the **default app**: - * - * ```js - * const functionsForDefaultApp = firebase.functions(); - * ``` - * - * #### Example 2 - * - * Get the functions instance for a **secondary app**: - * - * ```js - * const otherApp = firebase.app('otherApp'); - * const functionsForOtherApp = firebase.functions(otherApp); - * ``` - * - * #### Example 3 - * - * Get the functions instance for a **specific functions region**: - * - * ```js - * const defaultApp = firebase.app(); - * const functionsForRegion = defaultApp.functions('europe-west1'); - * - * const otherApp = firebase.app('otherApp'); - * const functionsForOtherAppRegion = otherApp.functions('europe-west1'); - * ``` - * - */ - export class Module extends FirebaseModule { - /** - * Returns a reference to the callable HTTPS trigger with the given name. - * - * #### Example - * - * ```js - * const reference = firebase.functions().httpsCallable('order'); - * - * try { - * const response = await reference({ - * id: '12345', - * }); - * } catch (e) { - * console.error(e); - * } - * ``` - * - * @param name The name of the https callable function. - * @return The `HttpsCallable` reference. - */ - httpsCallable( - name: string, - options?: HttpsCallableOptions, - ): HttpsCallable; - - /** - * Returns a reference to the callable HTTPS trigger with the specified url. - * - * #### Example - * - * ```js - * const reference = firebase.functions().httpsCallable('order'); - * - * try { - * const response = await reference({ - * id: '12345', - * }); - * } catch (e) { - * console.error(e); - * } - * ``` - * - * @param name The name of the https callable function. - * @return The `HttpsCallable` reference. - */ - httpsCallableFromUrl( - url: string, - options?: HttpsCallableOptions, - ): HttpsCallable; - - /** - * Changes this instance to point to a Cloud Functions emulator running locally. - * - * See https://firebase.google.com/docs/functions/local-emulator - * - * #### Example - * - * ```js - * if (__DEV__) { - * firebase.functions().useFunctionsEmulator('http://localhost:5001'); - * } - * ``` - * - * Note: on android, hosts 'localhost' and '127.0.0.1' are automatically remapped to '10.0.2.2' (the - * "host" computer IP address for android emulators) to make the standard development experience easy. - * If you want to use the emulator on a real android device, you will need to specify the actual host - * computer IP address. - * - * @deprecated prefer useEmulator instead - * @param origin url of the local emulator started via firebase tools "http://localhost:5001" - */ - useFunctionsEmulator(origin: string): void; - - /** - * Changes this instance to point to a Cloud Functions emulator running locally. - * - * See https://firebase.google.com/docs/functions/local-emulator - * - * #### Example - * - * ```js - * if (__DEV__) { - * firebase.functions().useEmulator('localhost', 5001); - * } - * ``` - * - * Note: on android, hosts 'localhost' and '127.0.0.1' are automatically remapped to '10.0.2.2' (the - * "host" computer IP address for android emulators) to make the standard development experience easy. - * If you want to use the emulator on a real android device, you will need to specify the actual host - * computer IP address. - * - * @param host hostname of the local emulator started via firebase tools, ex. "localhost" - * @param port port of the local emulator started via firebase tools, ex. 5001 - */ - useEmulator(host: string, port: number): void; - } -} - -declare const defaultExport: ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< - FirebaseFunctionsTypes.Module, - FirebaseFunctionsTypes.Statics ->; - -export const firebase: ReactNativeFirebase.Module & { - functions: typeof defaultExport; - app( - name?: string, - ): ReactNativeFirebase.FirebaseApp & { functions(): FirebaseFunctionsTypes.Module }; -}; - -export default defaultExport; - -export * from './modular'; - -/** - * Attach namespace to `firebase.` and `FirebaseApp.`. - */ -declare module '@react-native-firebase/app' { - namespace ReactNativeFirebase { - import FirebaseModuleWithStaticsAndApp = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; - interface Module { - functions: FirebaseModuleWithStaticsAndApp< - FirebaseFunctionsTypes.Module, - FirebaseFunctionsTypes.Statics - >; - } - interface FirebaseApp { - functions(customUrlOrRegion?: string): FirebaseFunctionsTypes.Module; - } - } -} diff --git a/packages/functions/lib/index.ts b/packages/functions/lib/index.ts new file mode 100644 index 0000000000..055640ce64 --- /dev/null +++ b/packages/functions/lib/index.ts @@ -0,0 +1,161 @@ +/* + * 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 { getApp, type ReactNativeFirebase } from '@react-native-firebase/app'; +import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/lib/common'; + +type Functions = FunctionsModule; +type FirebaseApp = ReactNativeFirebase.FirebaseApp & { + functions(regionOrCustomDomain?: string): Functions; +}; + +interface HttpsCallableOptions { + timeout?: number; +} + +// Export types for use in modular API +export type { HttpsCallableOptions }; + +export interface HttpsCallable { + (data?: RequestData | null): Promise<{ data: ResponseData }>; +} + +export interface FunctionsModule { + httpsCallable( + name: string, + options?: HttpsCallableOptions, + ): HttpsCallable; + httpsCallableFromUrl( + url: string, + options?: HttpsCallableOptions, + ): HttpsCallable; + useFunctionsEmulator(origin: string): void; + useEmulator(host: string, port: number): void; +} + +/** + * Returns a Functions instance for the given app. + * @param app - The FirebaseApp to use. Optional. + * @param regionOrCustomDomain - One of: a) The region the callable functions are located in (ex: us-central1) b) A custom domain hosting the callable functions (ex: https://mydomain.com). Optional. + * @returns Functions instance for the given app. + */ +export function getFunctions(app?: FirebaseApp, regionOrCustomDomain?: string): Functions { + if (app) { + return getApp(app.name).functions(regionOrCustomDomain) as Functions; + } + + return getApp().functions(regionOrCustomDomain) as Functions; +} + +/** + * Modify this instance to communicate with the Cloud Functions emulator. + * Note: this must be called before this instance has been used to do any operations. + * @param functionsInstance A functions instance. + * @param host The emulator host. (ex: localhost) + * @param port The emulator port. (ex: 5001) + */ +export function connectFunctionsEmulator( + functionsInstance: Functions, + host: string, + port: number, +): void { + // @ts-ignore + return functionsInstance.useEmulator.call(functionsInstance, host, port, MODULAR_DEPRECATION_ARG); +} + +/** + * Returns a reference to the callable HTTPS trigger with the given name. + * @param functionsInstance A functions instance. + * @param name The name of the trigger. + * @param options An interface for metadata about how calls should be executed. + * @returns HttpsCallable instance + */ +export function httpsCallable( + functionsInstance: Functions, + name: string, + options?: HttpsCallableOptions, +): HttpsCallable { + return functionsInstance.httpsCallable.call( + functionsInstance, + name, + options, + // @ts-ignore + MODULAR_DEPRECATION_ARG, + ) as HttpsCallable; +} + +/** + * Returns a reference to the callable HTTPS trigger with the specified url. + * @param functionsInstance A functions instance. + * @param url The url of the trigger. + * @param options An instance of HttpsCallableOptions containing metadata about how calls should be executed. + * @returns HttpsCallable instance + */ +export function httpsCallableFromUrl( + functionsInstance: Functions, + url: string, + options?: HttpsCallableOptions, +): HttpsCallable { + return functionsInstance.httpsCallableFromUrl.call( + functionsInstance, + url, + options, + // @ts-ignore + MODULAR_DEPRECATION_ARG, + ) as HttpsCallable; +} + +// Define HttpsErrorCode locally to avoid circular imports +export const HttpsErrorCode = { + OK: 'ok', + CANCELLED: 'cancelled', + UNKNOWN: 'unknown', + INVALID_ARGUMENT: 'invalid-argument', + DEADLINE_EXCEEDED: 'deadline-exceeded', + NOT_FOUND: 'not-found', + ALREADY_EXISTS: 'already-exists', + PERMISSION_DENIED: 'permission-denied', + UNAUTHENTICATED: 'unauthenticated', + RESOURCE_EXHAUSTED: 'resource-exhausted', + FAILED_PRECONDITION: 'failed-precondition', + ABORTED: 'aborted', + OUT_OF_RANGE: 'out-of-range', + UNIMPLEMENTED: 'unimplemented', + INTERNAL: 'internal', + UNAVAILABLE: 'unavailable', + DATA_LOSS: 'data-loss', + // Web codes are lowercase dasherized. + ok: 'ok', + cancelled: 'cancelled', + unknown: 'unknown', + 'invalid-argument': 'invalid-argument', + 'deadline-exceeded': 'deadline-exceeded', + 'not-found': 'not-found', + 'already-exists': 'already-exists', + 'permission-denied': 'permission-denied', + unauthenticated: 'unauthenticated', + 'resource-exhausted': 'resource-exhausted', + 'failed-precondition': 'failed-precondition', + aborted: 'aborted', + 'out-of-range': 'out-of-range', + unimplemented: 'unimplemented', + internal: 'internal', + unavailable: 'unavailable', + 'data-loss': 'data-loss', +} as const; + +export * from './namespaced'; diff --git a/packages/functions/lib/modular/index.d.ts b/packages/functions/lib/modular/index.d.ts deleted file mode 100644 index cee948f95f..0000000000 --- a/packages/functions/lib/modular/index.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ReactNativeFirebase } from '@react-native-firebase/app'; -import { FirebaseFunctionsTypes } from '..'; - -import FirebaseApp = ReactNativeFirebase.FirebaseApp; -import Functions = FirebaseFunctionsTypes.Module; -import HttpsCallable = FirebaseFunctionsTypes.HttpsCallable; -import HttpsCallableOptions = FirebaseFunctionsTypes.HttpsCallableOptions; -import HttpsErrorCodeType = FirebaseFunctionsTypes.HttpsErrorCode; - -export const HttpsErrorCode: HttpsErrorCodeType; - -/** - * Get a {@link Functions} instance for the given app. - * @param {FirebaseApp | undefined} app - The FirebaseApp to use. Optional. - * @param {string | undefined} regionOrCustomDomain - One of: a) The region the callable functions are located in (ex: us-central1) b) A custom domain hosting the callable functions (ex: https://mydomain.com). optional - * @returns {Functions} Returns a {@link Functions} instance for the given app. - */ -export declare function getFunctions(app?: FirebaseApp, regionOrCustomDomain?: string): Functions; - -/** - * Modify this instance to communicate with the Cloud Functions emulator. - * Note: this must be called before this instance has been used to do any operations. - * @param {Functions} functionsInstance - * @param {string} host The emulator host. (ex: localhost) - * @param {number} port The emulator port. (ex: 5001) - */ -export declare function connectFunctionsEmulator( - functionsInstance: Functions, - host: string, - port: number, -): void; - -/** - * Returns a reference to the {@link HttpsCallable} trigger with the given name. - * @param {Functions} functionsInstance A functions instance. - * @param {string} name The name of the trigger. - * @param {HttpsCallableOptions | undefined} options An instance of {@link HttpsCallableOptions} containing metadata about how calls should be executed. - * @returns {HttpsCallable} - */ -export declare function httpsCallable( - functionsInstance: Functions, - name: string, - options?: HttpsCallableOptions, -): HttpsCallable; - -/** - * Returns a reference to the {@link HttpsCallable} trigger with the specified url. - * @param {Functions} functionsInstance A functions instance. - * @param {string} url The url of the trigger. - * @param {HttpsCallableOptions | undefined} options An instance of {@link HttpsCallableOptions} containing metadata about how calls should be executed. - * @returns {HttpsCallable} - */ -export declare function httpsCallableFromUrl( - functionsInstance: Functions, - url: string, - options?: HttpsCallableOptions, -): HttpsCallable; diff --git a/packages/functions/lib/modular/index.js b/packages/functions/lib/modular/index.js deleted file mode 100644 index 96a076a28b..0000000000 --- a/packages/functions/lib/modular/index.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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. - * - */ -/** - * @typedef {import("..").FirebaseFunctionsTypes.Module} Functions - * @typedef {import("..").FirebaseFunctionsTypes.HttpsCallable} HttpsCallable - * @typedef {import("..").FirebaseFunctionsTypes.HttpsCallableOptions} HttpsCallableOptions - * @typedef {import("@firebase/app").FirebaseApp} FirebaseApp - */ - -import { getApp } from '@react-native-firebase/app'; -import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/lib/common'; - -/** - * Returns a Functions instance for the given app. - * @param {FirebaseApp | undefined} app - The FirebaseApp to use. Optional. - * @param {string | undefined} regionOrCustomDomain - One of: a) The region the callable functions are located in (ex: us-central1) b) A custom domain hosting the callable functions (ex: https://mydomain.com). Optional. - * @returns {Functions} - */ -export function getFunctions(app, regionOrCustomDomain) { - if (app) { - return getApp(app.name).functions(regionOrCustomDomain); - } - - return getApp().functions(regionOrCustomDomain); -} - -/** - * Modify this instance to communicate with the Cloud Functions emulator. - * Note: this must be called before this instance has been used to do any operations. - * @param {Functions} functionsInstance A functions instance. - * @param {string} host The emulator host. (ex: localhost) - * @param {number} port The emulator port. (ex: 5001) - * @returns {void} - */ -export function connectFunctionsEmulator(functionsInstance, host, port) { - return functionsInstance.useEmulator.call(functionsInstance, host, port, MODULAR_DEPRECATION_ARG); -} - -/** - * Returns a reference to the callable HTTPS trigger with the given name. - * @param {Functions} functionsInstance A functions instance. - * @param {string} name The name of the trigger. - * @param {HttpsCallableOptions | undefined} options An interface for metadata about how calls should be executed. - * @returns {HttpsCallable} - */ -export function httpsCallable(functionsInstance, name, options) { - return functionsInstance.httpsCallable.call( - functionsInstance, - name, - options, - MODULAR_DEPRECATION_ARG, - ); -} - -/** - * Returns a reference to the callable HTTPS trigger with the specified url. - * @param {Functions} functionsInstance A functions instance. - * @param {string} url The url of the trigger. - * @param {HttpsCallableOptions | undefined} options An instance of {@link HttpsCallableOptions} containing metadata about how calls should be executed. - * @returns {HttpsCallable} - */ -export function httpsCallableFromUrl(functionsInstance, url, options) { - return functionsInstance.httpsCallableFromUrl.call( - functionsInstance, - url, - options, - MODULAR_DEPRECATION_ARG, - ); -} - -export { HttpsErrorCode } from '../index'; diff --git a/packages/functions/lib/index.js b/packages/functions/lib/namespaced.ts similarity index 61% rename from packages/functions/lib/index.js rename to packages/functions/lib/namespaced.ts index cc038403c0..9a52404a95 100644 --- a/packages/functions/lib/index.js +++ b/packages/functions/lib/namespaced.ts @@ -21,13 +21,15 @@ import { FirebaseModule, getFirebaseRoot, } from '@react-native-firebase/app/lib/internal'; -import HttpsError from './HttpsError'; -import version from './version'; +import { HttpsError, type NativeError } from './HttpsError'; +import { version } from './version'; import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule'; import fallBackModule from './web/RNFBFunctionsModule'; - +import type { HttpsCallableOptions } from '.'; +import type { FirebaseApp } from '@react-native-firebase/app'; const namespace = 'functions'; -const nativeModuleName = 'RNFBFunctionsModule'; + +const nativeModuleName = 'NativeRNFBTurboFunctions'; export const HttpsErrorCode = { OK: 'ok', @@ -67,21 +69,102 @@ export const HttpsErrorCode = { internal: 'internal', unavailable: 'unavailable', 'data-loss': 'data-loss', -}; +} as const; const statics = { HttpsErrorCode, }; +// Export the complete FirebaseFunctionsTypes namespace +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace FirebaseFunctionsTypes { + export type FunctionsErrorCode = + | 'ok' + | 'cancelled' + | 'unknown' + | 'invalid-argument' + | 'deadline-exceeded' + | 'not-found' + | 'already-exists' + | 'permission-denied' + | 'resource-exhausted' + | 'failed-precondition' + | 'aborted' + | 'out-of-range' + | 'unimplemented' + | 'internal' + | 'unavailable' + | 'data-loss' + | 'unauthenticated'; + + export interface HttpsCallableResult { + readonly data: ResponseData; + } + + export interface HttpsCallable { + (data?: RequestData | null): Promise>; + } + + export interface HttpsCallableOptions { + timeout?: number; + } + + export interface HttpsError extends Error { + readonly code: FunctionsErrorCode; + readonly details?: any; + } + + export interface HttpsErrorCode { + OK: 'ok'; + CANCELLED: 'cancelled'; + UNKNOWN: 'unknown'; + INVALID_ARGUMENT: 'invalid-argument'; + DEADLINE_EXCEEDED: 'deadline-exceeded'; + NOT_FOUND: 'not-found'; + ALREADY_EXISTS: 'already-exists'; + PERMISSION_DENIED: 'permission-denied'; + UNAUTHENTICATED: 'unauthenticated'; + RESOURCE_EXHAUSTED: 'resource-exhausted'; + FAILED_PRECONDITION: 'failed-precondition'; + ABORTED: 'aborted'; + OUT_OF_RANGE: 'out-of-range'; + UNIMPLEMENTED: 'unimplemented'; + INTERNAL: 'internal'; + UNAVAILABLE: 'unavailable'; + DATA_LOSS: 'data-loss'; + } + + export interface Statics { + HttpsErrorCode: HttpsErrorCode; + } + + export interface Module { + httpsCallable( + name: string, + options?: HttpsCallableOptions, + ): HttpsCallable; + httpsCallableFromUrl( + url: string, + options?: HttpsCallableOptions, + ): HttpsCallable; + useFunctionsEmulator(origin: string): void; + useEmulator(host: string, port: number): void; + } +} + class FirebaseFunctionsModule extends FirebaseModule { - constructor(...args) { - super(...args); - this._customUrlOrRegion = this._customUrlOrRegion || 'us-central1'; + _customUrlOrRegion: string; + private _useFunctionsEmulatorHost: string | null; + private _useFunctionsEmulatorPort: 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; } - httpsCallable(name, options = {}) { + httpsCallable(name: string, options: HttpsCallableOptions = {}) { if (options.timeout) { if (isNumber(options.timeout)) { options.timeout = options.timeout / 1000; @@ -90,7 +173,7 @@ class FirebaseFunctionsModule extends FirebaseModule { } } - return data => { + return (data?: any) => { const nativePromise = this.native.httpsCallable( this._useFunctionsEmulatorHost, this._useFunctionsEmulatorPort, @@ -100,11 +183,11 @@ class FirebaseFunctionsModule extends FirebaseModule { }, options, ); - return nativePromise.catch(nativeError => { + return nativePromise.catch((nativeError: NativeError) => { const { code, message, details } = nativeError.userInfo || {}; return Promise.reject( new HttpsError( - HttpsErrorCode[code] || HttpsErrorCode.UNKNOWN, + HttpsErrorCode[code as keyof typeof HttpsErrorCode] || HttpsErrorCode.UNKNOWN, message || nativeError.message, details || null, nativeError, @@ -114,7 +197,7 @@ class FirebaseFunctionsModule extends FirebaseModule { }; } - httpsCallableFromUrl(url, options = {}) { + httpsCallableFromUrl(url: string, options: HttpsCallableOptions = {}) { if (options.timeout) { if (isNumber(options.timeout)) { options.timeout = options.timeout / 1000; @@ -123,7 +206,7 @@ class FirebaseFunctionsModule extends FirebaseModule { } } - return data => { + return (data?: any) => { const nativePromise = this.native.httpsCallableFromUrl( this._useFunctionsEmulatorHost, this._useFunctionsEmulatorPort, @@ -133,11 +216,11 @@ class FirebaseFunctionsModule extends FirebaseModule { }, options, ); - return nativePromise.catch(nativeError => { + return nativePromise.catch((nativeError: NativeError) => { const { code, message, details } = nativeError.userInfo || {}; return Promise.reject( new HttpsError( - HttpsErrorCode[code] || HttpsErrorCode.UNKNOWN, + HttpsErrorCode[code as keyof typeof HttpsErrorCode] || HttpsErrorCode.UNKNOWN, message || nativeError.message, details || null, nativeError, @@ -147,15 +230,17 @@ class FirebaseFunctionsModule extends FirebaseModule { }; } - useFunctionsEmulator(origin) { - [_, host, port] = /https?\:.*\/\/([^:]+):?(\d+)?/.exec(origin); - if (!port) { - port = 5001; + useFunctionsEmulator(origin: string): void { + const match = /https?\:.*\/\/([^:]+):?(\d+)?/.exec(origin); + if (!match) { + throw new Error('Invalid emulator origin format'); } - this.useEmulator(host, parseInt(port)); + const [, host, portStr] = match; + const port = portStr ? parseInt(portStr) : 5001; + this.useEmulator(host as string, port); } - useEmulator(host, port) { + useEmulator(host: string, port: number): void { if (!isNumber(port)) { throw new Error('useEmulator port parameter must be a number'); } @@ -200,10 +285,9 @@ export default createModuleNamespace({ hasMultiAppSupport: true, hasCustomUrlOrRegionSupport: true, ModuleClass: FirebaseFunctionsModule, + turboModule: true, }); -export * from './modular'; - // import functions, { firebase } from '@react-native-firebase/functions'; // functions().logEvent(...); // firebase.functions().logEvent(...); diff --git a/packages/functions/lib/types.d.ts b/packages/functions/lib/types.d.ts new file mode 100644 index 0000000000..7e6520ecfc --- /dev/null +++ b/packages/functions/lib/types.d.ts @@ -0,0 +1,58 @@ +declare module '@react-native-firebase/app/lib/common' { + export const MODULAR_DEPRECATION_ARG: string; + export const isAndroid: boolean; + export const isNumber: (value: any) => value is number; +} + +declare module '@react-native-firebase/app/lib/internal' { + export function createModuleNamespace(config: any): any; + export class FirebaseModule { + constructor(...args: any[]); + native: any; + firebaseJson: any; + _customUrlOrRegion: string | null; + } + export function getFirebaseRoot(): any; + export class NativeFirebaseError { + static getStackWithMessage(message: string, jsStack?: string): string; + } +} + +declare module '@react-native-firebase/app/lib/internal/nativeModule' { + export function setReactNativeModule(moduleName: string, module: any): void; +} + +declare module '@react-native-firebase/app/lib' { + namespace ReactNativeFirebase { + import FirebaseModuleWithStaticsAndApp = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; + interface Module { + functions: FirebaseModuleWithStaticsAndApp; + } + interface FirebaseApp { + functions(customUrlOrRegion?: string): any; + readonly name: string; + } + } +} + +declare module '@react-native-firebase/app/lib/internal/web/firebaseFunctions' { + export function getApp(appName: string): any; + export function getFunctions(app: any, regionOrCustomDomain?: string): any; + export function httpsCallable(functionsInstance: any, name: string, options?: any): any; + export function httpsCallableFromURL(functionsInstance: any, url: string, options?: any): any; + export function connectFunctionsEmulator( + functionsInstance: any, + host: string, + port: number, + ): void; +} + +declare module './version' { + const version: string; + export default version; +} + +declare module './web/RNFBFunctionsModule' { + const fallBackModule: any; + export default fallBackModule; +} diff --git a/packages/functions/lib/web/RNFBFunctionsModule.android.js b/packages/functions/lib/web/RNFBFunctionsModule.android.ts similarity index 100% rename from packages/functions/lib/web/RNFBFunctionsModule.android.js rename to packages/functions/lib/web/RNFBFunctionsModule.android.ts diff --git a/packages/functions/lib/web/RNFBFunctionsModule.ios.js b/packages/functions/lib/web/RNFBFunctionsModule.ios.ts similarity index 100% rename from packages/functions/lib/web/RNFBFunctionsModule.ios.js rename to packages/functions/lib/web/RNFBFunctionsModule.ios.ts diff --git a/packages/functions/lib/web/RNFBFunctionsModule.js b/packages/functions/lib/web/RNFBFunctionsModule.ts similarity index 63% rename from packages/functions/lib/web/RNFBFunctionsModule.js rename to packages/functions/lib/web/RNFBFunctionsModule.ts index cfb29c2281..7d4b7b397d 100644 --- a/packages/functions/lib/web/RNFBFunctionsModule.js +++ b/packages/functions/lib/web/RNFBFunctionsModule.ts @@ -5,6 +5,12 @@ import { httpsCallableFromURL, connectFunctionsEmulator, } from '@react-native-firebase/app/lib/internal/web/firebaseFunctions'; +import type { HttpsCallableOptions } from '../index'; +import type { NativeError } from '../HttpsError'; + +interface WrapperData { + data?: any; +} /** * This is a 'NativeModule' for the web platform. @@ -15,16 +21,24 @@ import { export default { /** * Get and execute a Firebase Functions callable. - * @param {string} appName - The name of the app to get the functions instance for. - * @param {string} regionOrCustomDomain - The region or custom domain to use for the functions instance. - * @param {string} host - The host to use for the functions emulator. - * @param {number} port - The port to use for the functions emulator. - * @param {string} name - The name of the functions callable. - * @param {object} wrapper - The wrapper object to use for the functions callable. - * @param {object} options - The options to use for the functions callable. - * @returns {object} - The result of the functions callable. + * @param appName - The name of the app to get the functions instance for. + * @param regionOrCustomDomain - The region or custom domain to use for the functions instance. + * @param host - The host to use for the functions emulator. + * @param port - The port to use for the functions emulator. + * @param name - The name of the functions callable. + * @param wrapper - The wrapper object to use for the functions callable. + * @param options - The options to use for the functions callable. + * @returns The result of the functions callable. */ - async httpsCallable(appName, regionOrCustomDomain, host, port, name, wrapper, options) { + async httpsCallable( + appName: string, + regionOrCustomDomain: string | null, + host: string | null, + port: number, + name: string, + wrapper: WrapperData, + options: HttpsCallableOptions, + ): Promise { try { const app = getApp(appName); let functionsInstance; @@ -58,10 +72,9 @@ export default { const data = wrapper['data'] ?? null; const result = await callable(data); return result; - } catch (error) { + } catch (error: any) { const { code, message, details } = error; - const nativeError = { - code, + const nativeError: NativeError = { message, userInfo: { code: code ? code.replace('functions/', '') : 'unknown', @@ -75,22 +88,30 @@ export default { /** * Get and execute a Firebase Functions callable from a URL. - * @param {string} appName - The name of the app to get the functions instance for. - * @param {string} regionOrCustomDomain - The region or custom domain to use for the functions instance. - * @param {string} host - The host to use for the functions emulator. - * @param {number} port - The port to use for the functions emulator. - * @param {string} url - The URL to use for the functions callable. - * @param {object} wrapper - The wrapper object to use for the functions callable. - * @param {object} options - The options to use for the functions callable. - * @returns {object} - The result of the functions callable. + * @param appName - The name of the app to get the functions instance for. + * @param regionOrCustomDomain - The region or custom domain to use for the functions instance. + * @param host - The host to use for the functions emulator. + * @param port - The port to use for the functions emulator. + * @param url - The URL to use for the functions callable. + * @param wrapper - The wrapper object to use for the functions callable. + * @param options - The options to use for the functions callable. + * @returns The result of the functions callable. */ - async httpsCallableFromUrl(appName, regionOrCustomDomain, host, port, url, wrapper, options) { + async httpsCallableFromUrl( + appName: string, + regionOrCustomDomain: string | null, + host: string | null, + port: number, + url: string, + wrapper: WrapperData, + options: HttpsCallableOptions, + ): Promise { try { const app = getApp(appName); let functionsInstance; if (regionOrCustomDomain) { functionsInstance = getFunctions(app, regionOrCustomDomain); - // Hack to work around custom domain and` region not being set on the instance. + // Hack to work around custom domain and region not being set on the instance. if (regionOrCustomDomain.startsWith('http')) { functionsInstance.customDomain = regionOrCustomDomain; } else { @@ -109,10 +130,9 @@ export default { const callable = httpsCallableFromURL(functionsInstance, url, options); const result = await callable(wrapper['data']); return result; - } catch (error) { + } catch (error: any) { const { code, message, details } = error; - const nativeError = { - code, + const nativeError: NativeError = { message, userInfo: { code: code ? code.replace('functions/', '') : 'unknown', diff --git a/packages/functions/package.json b/packages/functions/package.json index 2383459ee0..45eaa51d4d 100644 --- a/packages/functions/package.json +++ b/packages/functions/package.json @@ -3,12 +3,31 @@ "version": "23.5.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Cloud Functions for Firebase lets you automatically run backend code in response to events triggered by Firebase features and HTTPS requests. React Native Firebase supports integration with production and locally emulated Cloud Functions with a simple API interface.\n\n", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/module/index.js", + "types": "./dist/typescript/commonjs/lib/index.d.ts", + "codegenConfig": { + "name": "NativeRNFBTurboFunctions", + "type": "modules", + "jsSrcsDir": "specs", + "includesGeneratedCode": true, + "android": { + "javaPackageName": "io.invertase.firebase.functions" + }, + "ios": { + "modulesProvider": { + "NativeRNFBTurboFunctions": "RNFBFunctionsModule" + } + } + }, "scripts": { - "build": "genversion --semi lib/version.js", + "build": "genversion --esm --semi lib/version.ts", "build:clean": "rimraf android/build && rimraf ios/build", - "prepare": "yarn run build" + "compile": "bob build", + "prepare": "yarn run build && yarn compile", + "codegen": "yarn android:codegen && yarn ios:codegen", + "android:codegen": "npx @react-native-community/cli codegen --platform android --outputPath=./android/src/main/java/io/invertase/firebase/functions/generated", + "ios:codegen": "npx @react-native-community/cli codegen --platform ios --outputPath=./ios/generated" }, "repository": { "type": "git", @@ -27,10 +46,62 @@ "@react-native-firebase/app": "23.5.0" }, "devDependencies": { - "@react-native-firebase/private-tests-firebase-functions": "^0.0.1" + "@react-native-firebase/private-tests-firebase-functions": "^0.0.1", + "react-native": "^0.80.1", + "react-native-builder-bob": "^0.40.12", + "typescript": "^5.8.3" }, "publishConfig": { "access": "public", "provenance": true - } + }, + "exports": { + ".": { + "source": "./lib/index.ts", + "import": { + "types": "./dist/typescript/module/lib/index.d.ts", + "default": "./dist/module/index.js" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "lib/modular", + "dist", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "react-native-builder-bob": { + "source": "lib", + "output": "dist", + "targets": [ + [ + "module", + { + "esm": true + } + ], + [ + "commonjs", + { + "esm": true + } + ], + [ + "typescript", + { + "tsc": "../../node_modules/.bin/tsc" + } + ] + ] + }, + "eslintIgnore": [ + "node_modules/", + "dist/" + ] } diff --git a/packages/functions/react-native.config.js b/packages/functions/react-native.config.js new file mode 100644 index 0000000000..d1b6f36785 --- /dev/null +++ b/packages/functions/react-native.config.js @@ -0,0 +1,10 @@ +module.exports = { + dependency: { + platforms: { + android: { + cmakeListsPath: + './src/main/java/io/invertase/firebase/functions/generated/jni/CMakeLists.txt', + }, + }, + }, +}; diff --git a/packages/functions/specs/NativeRNFBTurboFunctions.ts b/packages/functions/specs/NativeRNFBTurboFunctions.ts new file mode 100644 index 0000000000..63d60ba666 --- /dev/null +++ b/packages/functions/specs/NativeRNFBTurboFunctions.ts @@ -0,0 +1,50 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +// Define generic types outside the interface +export type RequestData = unknown; +export type ResponseData = unknown; + +export interface Spec extends TurboModule { + /** + * Calls a Cloud Function with the given name and data. + * + * @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 + * @returns Promise that resolves with the function result + */ + httpsCallable( + appName: string, + region: string, + emulatorHost: string | null, + emulatorPort: number, + name: string, + data: { data: RequestData }, + options: { timeout?: number }, + ): Promise<{ data: ResponseData }>; + + /** + * Calls a Cloud Function using a full URL instead of just the function name. + * + * @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 + * @returns Promise that resolves with the function result + */ + httpsCallableFromUrl( + appName: string, + region: string, + emulatorHost: string | null, + emulatorPort: number, + url: string, + data: { data: RequestData }, + options: { timeout?: number }, + ): Promise<{ data: ResponseData }>; +} + +export default TurboModuleRegistry.getEnforcing('NativeRNFBTurboFunctions'); diff --git a/packages/functions/tsconfig.json b/packages/functions/tsconfig.json new file mode 100644 index 0000000000..8bf433ac43 --- /dev/null +++ b/packages/functions/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "lib": [ + "ESNext" + ], + "module": "ESNext", + "moduleResolution": "bundler", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "verbatimModuleSyntax": true, + "baseUrl": ".", + "rootDir": ".", + "paths": { + "@react-native-firebase/app": ["../app/lib"] + } + }, + "include": ["lib/**/*"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] +} diff --git a/packages/functions/type-test.ts b/packages/functions/type-test.ts deleted file mode 100644 index 44a1b399d0..0000000000 --- a/packages/functions/type-test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import firebase, { FirebaseFunctionsTypes } from '.'; - -console.log(firebase().app); - -// checks module exists at root -console.log(firebase.functions().app.name); - -// checks module exists at app level -console.log(firebase.app().functions().app.name); - -// app level module accepts string arg -console.log(firebase.app().functions('some-string').app.name); -console.log(firebase.app().functions('some-string').httpsCallable('foo')); - -// checks statics exist -console.log(firebase.functions.SDK_VERSION); - -// checks statics exist on defaultExport -console.log(firebase.firebase.SDK_VERSION); - -// checks root exists -console.log(firebase.SDK_VERSION); - -// checks multi-app support exists -console.log(firebase.functions(firebase.app()).app.name); - -// checks default export supports app arg -console.log(firebase.functions(firebase.app('foo')).app.name); - -console.log(firebase.functions.HttpsErrorCode.ABORTED); - -firebase - .functions() - .httpsCallable('foo')(123) - .then((result: FirebaseFunctionsTypes.HttpsCallableResult) => { - console.log(result.data); - }) - .catch((error: { code: any; details: any }) => { - console.log(error.code); - console.log(error.details); - }); - -firebase.functions().useFunctionsEmulator('123'); diff --git a/packages/remote-config/e2e/config.e2e.js b/packages/remote-config/e2e/config.e2e.js index fd95816de4..44fa77ce6e 100644 --- a/packages/remote-config/e2e/config.e2e.js +++ b/packages/remote-config/e2e/config.e2e.js @@ -697,8 +697,8 @@ describe('remoteConfig()', function () { (await isSupported()).should.equal(true); }); }); - - describe('onConfigUpdate', function () { + // SKIPPED: Skipping suite of onConfigUpdate tests until they're fixed + describe.skip('onConfigUpdate', function () { describe('onConfigUpdate parameter verification', function () { it('throws an error if no callback provided', async function () { const { getRemoteConfig, onConfigUpdate } = remoteConfigModular; @@ -931,8 +931,8 @@ describe('remoteConfig()', function () { }); }); - // deprecated API prior to official web support - describe('onConfigUpdated (deprecated)', function () { + // SKIPPED: Skipping suite of onConfigUpdate tests until they're fixed + describe.skip('onConfigUpdated (deprecated)', function () { describe('onConfigUpdated parameter verification', function () { it('throws an error if no callback provided', async function () { const { getRemoteConfig, onConfigUpdated } = remoteConfigModular; diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index 25f8c14ab4..52dd193f25 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -1840,7 +1840,11 @@ PODS: - RNFBApp - RNFBFunctions (23.5.0): - Firebase/Functions (= 12.6.0) + - RCT-Folly - React-Core + - React-Fabric + - ReactAppDependencyProvider + - ReactCodegen - RNFBApp - RNFBInAppMessaging (23.5.0): - Firebase/InAppMessaging (= 12.6.0) @@ -2235,85 +2239,85 @@ SPEC CHECKSUMS: nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 + RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 RCTDeprecation: cf39863b43871c2031050605fb884019b6193910 RCTRequired: 8fdd66f4a97f352b66f38cfef13fc11b12d2c884 RCTTypeSafety: c9c9e64389bc545fc137030615b387ef1654dcee React: 14a80ea4f13387cfdaa4250b46fbfe19754c220c React-callinvoker: fed1dad5d6cf992c7b4b5fdbf1bf67fe2e8fb6c5 - React-Core: 3c803e7f3be6fa68e3dabcac283a5a5f87340a60 - React-CoreModules: 94d556b4055defb79278c3afba95e521998b9b3a - React-cxxreact: 21c826a567199cc2d5797bd4cfe3c0d94bb3a7de + React-Core: f703e7a56fcedc3e959b8b7899995e57fd58539a + React-CoreModules: 6e87c904cc257058c271708eef1719b5b3039131 + React-cxxreact: 4153beeff710944832cd90ccb141e299ee16b7d3 React-debug: a665bbe67eb786b7a7a815ce1b7212b3f9fa962c - React-defaultsnativemodule: 8f0bea8d0d3434aa98957302f2a8741600781edd - React-domnativemodule: 889d261cc9691b91063a1c1d89d408e19903923d - React-Fabric: eb0bde19a858807eee7d4fee07f24f036bdf5e6a - React-FabricComponents: 7756ddcd87ff121d0dedff69c80fd6177ccd31d9 - React-FabricImage: c3096fe8c20a4aec77c54c271f878948efa9d477 + React-defaultsnativemodule: 7e4a2c2b13ec2943f2f3b8adec32130443909de6 + React-domnativemodule: dffaa482180243bd1e2b7fba329fd4adc12a2445 + React-Fabric: bd742f0ddb6073ff14f7e51075bc0380b84e7f7a + React-FabricComponents: 347a74f275f989b493ab5324a2161e6b3de5556e + React-FabricImage: f9da31053da5aae09b7f67afdc46329c6e3a2710 React-featureflags: 55800b546a28b63a8a0f419e1a45871d43523d32 - React-featureflagsnativemodule: b200bca78e00f9e5c7cd5a1c9f2957223fcfa33a - React-graphics: fb41a6a55ecd473159452c75b3ea7b57794903a3 - React-hermes: 3be7f73615e70b310b0457146691d39ea3752e6b - React-idlecallbacksnativemodule: dc10ee2e5ba5ae7ad56aa093aedda582345bce16 - React-ImageManager: 08592583c7737aec2b2c2a12e7c4f0ad763ae5c4 - React-jserrorhandler: eede06f57f67c8d3978ff1a9c78107aea5cbdf45 - React-jsi: 70ca5cce94e7f81e4ff51a71b4720b5fb943eea5 - React-jsiexecutor: 265d9fbb2a88c74892646e0012cec62ebb89edcf - React-jsinspector: 7a7e033c64cc40e6a1d6daf7fad4827bc1e7dd12 - React-jsinspectortracing: 77f4d18502af6e7599e77390b35f04f02814f7ce - React-jsitracing: 0608ea7ee711370d42fdd2b4a474dbced68dd275 - React-logger: 8d00d3d794041a77bd890158353361e2709b04c1 - React-Mapbuffer: 45ca4d30efe99834a8cd8d98f803c94766db534f - React-microtasksnativemodule: b5901a0b15f92ce0666ee5131eb8ab646f1d5a27 - React-NativeModulesApple: 7a9ec626a1852d444d0e699b016dc55a064b7569 - React-perflogger: d06f0fd0727356651a5535f6d717130187aeb667 - React-performancetimeline: c397114f2c025aa73412a9f21e021b08127fe820 + React-featureflagsnativemodule: a0ea334fdd3342a2e4dc05085c3e7653e16839d3 + React-graphics: 7360f5f3611fd5982aa0de772a73987ab401fb02 + React-hermes: a942bebef5e9fcc31f51c6fb814e96c260a2a20d + React-idlecallbacksnativemodule: 74d091304aad1ceb0c5b86e5dec14372fcdc0f34 + React-ImageManager: 6b2a95469d9a126f14410bbe10fb7378d37ed0e0 + React-jserrorhandler: 2643140639cbf047bf45f5c1a3ea95b04d748582 + React-jsi: b2de88284fc2cc69466a34d8b794160216d3bd2c + React-jsiexecutor: e947af1c9e42610affd9f4178cd4b649e8ac889b + React-jsinspector: 6d768dfb189027f7ff2161be31ccd69215426ded + React-jsinspectortracing: a6a70eb5c9d767d99391d0373330a7239fb6f9d0 + React-jsitracing: 69280997c7a80ac0af62b95f01a241d68581fb52 + React-logger: e6c3c1b55c18cc1b945b647ff9ada53e0e710b50 + React-Mapbuffer: 57bea44149376ecf1241dd9f02a6222adab43e57 + React-microtasksnativemodule: 2739fc8a8cb99486820c933ce560791c93be5337 + React-NativeModulesApple: 4849912ee050e6ae346f5789bd631570580d8b84 + React-perflogger: 069d41f741187be92ed46c6ac67add03b99f3166 + React-performancetimeline: eda794c5007eb6834e13bc6026a879b5f6d49c74 React-RCTActionSheet: a078d5008632fed31b0024c420ee02e612b317d5 - React-RCTAnimation: b197cc109a896c6ce23981e02e317cfc055f6fda - React-RCTAppDelegate: f7f1d7362256b7c142d9ab49f980df488101f869 - React-RCTBlob: c12d15d40db01ac3fe57c24d3ef5233ff3333da6 - React-RCTFabric: 8cdcde7157a22aac04dfeb579dfc3a1141446846 - React-RCTFBReactNativeSpec: c3a78cb9f2a98146443f1b732a4f21b2ce736abd - React-RCTImage: 7a3d9d67161c714fa4d9b93820da39a266d0f1ff - React-RCTLinking: f860b917500cd3974235a48d5b199a74a4ed6c26 - React-RCTNetwork: 6a984ab1b5a81d17a2df6cc02c24b249fb055deb - React-RCTSettings: e9a39068d8b60d78a5271dcb68f6ea7f59569cb2 - React-RCTText: 44457242238664a5ad69f06ec7a5f273a6967711 - React-RCTVibration: f448ad875c60b2ddc5fc5b06d3f5e2dfc3f18c09 + React-RCTAnimation: 82e31d191af4175e0c2df5bdac2c8569a5f3ab54 + React-RCTAppDelegate: a5c1ff79f5987462b4f62b27387459ba84012439 + React-RCTBlob: c462b8b7de6ce44ddc56dd96eebe1da0a6e54c77 + React-RCTFabric: 56b946204edb5d563885b3b045bdacbb387b27e7 + React-RCTFBReactNativeSpec: 8392ef66ad156cfa848546859bbff3b5e8a09458 + React-RCTImage: 10fad63f1bb8adbd519c4c2ef6bec3c0d95fdd32 + React-RCTLinking: 3843288a44dc33ec083c843f3ff31dd7d96ece41 + React-RCTNetwork: f237299bda8bbd56c4d01d2825110e40b75c438a + React-RCTSettings: c24ce1ee96c9b001ff5059ddd53412a20b7d5e71 + React-RCTText: d97cfb9c89b06de9530577dd43f178c47ea07853 + React-RCTVibration: 2fcefee071a4f0d416e4368416bb073ea6893451 React-rendererconsistency: c9f31b6d55877c5d49d25d69270b89f9cb208e26 - React-rendererdebug: 939c31f97f3bbf434832b7f73d8a438cf96ee1c4 + React-rendererdebug: 185ba0f801f29565873f7a37e410a812eddaa1ee React-rncore: 90e637179a4ce46643d445a9ef16f53af02a8d25 - React-RuntimeApple: 3df87718b4a8e438b4a02d1d7d47677dfcab35a5 - React-RuntimeCore: e188aa1b0fe0450f3a4e6098f2bb8d4b27c147cf + React-RuntimeApple: 713b7c24b3abed07fa39766b35deaabd679ba48e + React-RuntimeCore: 236d704919077fd3393a26fd0ecbaecc081ec94f React-runtimeexecutor: 2de0d537fc6d5b4a7074587b4459ea331c7e5715 - React-RuntimeHermes: 5aa429111da4c0a62d94b37ad5b5144deb8f49d0 - React-runtimescheduler: c3738ed7f0ba2e51b1612ec8d6cbe104c2b9b59a + React-RuntimeHermes: 3e87ad8c5160c976addacd679774a5e4fdb3c4b4 + React-runtimescheduler: bafaf0af0f68bd761d63ff4de3bf13e391820f79 React-timing: 7ad7dc61dfc93ceb4ec2b3e6d1a6ad9ad3652fe0 - React-utils: d6a3bec920c7fa710e8fb5b7c28d81fe54be8c75 - ReactAppDependencyProvider: 5df090fa3cbfc923c6bd0595b64d5ef6d89f7134 - ReactCodegen: 0c213020a601c6adda74f8826629bff9c6c408d3 - ReactCommon: 7f90ec8358d94ec2a078e11990f6e78542957b11 + React-utils: cf358d29b6802cca3d1bec20a939f2f5d50d40ba + ReactAppDependencyProvider: ad88c80e06f29900f2e6f9ccf1d4cb0bfc3e1bbc + ReactCodegen: 69c7aec61821e1860aaaf959189218ecca40e811 + ReactCommon: ef3e394efce4b78e9214587689c459cf82927368 RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba - RNCAsyncStorage: 6a8127b6987dc9fbce778669b252b14c8355c7ce - RNDeviceInfo: 36d7f232bfe7c9b5c494cb7793230424ed32c388 - RNFBAnalytics: 97c7df49a5316376c95fed222e219b811e7ee3d5 - RNFBApp: 177e3a5210c6fb436f511cc4413dad0e7c428bfd - RNFBAppCheck: d26dabdff94eb45f90fb7c8aa767306c85cdd26d - RNFBAppDistribution: 9c78e3cd8087e9c2fb0852e03fb08ace1480e1d7 - RNFBAuth: 4bcbf1b4b9e1024755dcab122d36a5dafba0e3f3 - RNFBCrashlytics: 61932725c42d3fed9eb255253b6a833162d6ee08 - RNFBDatabase: 4954117115f02550bb40ff0736017a60306e7775 - RNFBFirestore: 2602dab917e06e091b275984722a46c84b88ec48 - RNFBFunctions: 0bc8036021a91a9fbb4d28cae08c5b1089a307f3 - RNFBInAppMessaging: 54c3765e20ac9ceaa87afb727e1f18fe4a6a92ac - RNFBInstallations: 11a5da460997ba084484222580fe61785b38083a - RNFBMessaging: 0835c2f99911197fe95689fea3b4c8725af723f1 - RNFBML: 2cbc15122658e03c20f088a5c0b99dc116a417a4 - RNFBPerf: a976bdc076ef268d1d4ccdae69829d4e11bbc172 - RNFBRemoteConfig: ebca776a11b18ea0070cb3c6c9f29608e397caa3 - RNFBStorage: fe423da7035c0848bd2a051ff03fb97d5bb1107a + RNCAsyncStorage: 481acf401089f312189e100815088ea5dafc583c + RNDeviceInfo: 53f9c84e28e854a308d3e548e25ef120b4615531 + RNFBAnalytics: bd6653e12b1770b4f1b613adfb09f8966bc5156f + RNFBApp: d9e2a89c210b1f08fcf3096b15871d8dd896cdae + RNFBAppCheck: ceab2b24388cd1fd3a4e23de82e0c7a95bd4e210 + RNFBAppDistribution: 753dfc0956a35d319eba78828a32ea32ceb53e1d + RNFBAuth: eb8385644c405668ef77dc8dfd260283b1bc8b8c + RNFBCrashlytics: 2242d1973d2b7d487a0f7cb7a95227f6f90df940 + RNFBDatabase: 700462a4e6515b8ee80f3a20fbe872c57a9a42ad + RNFBFirestore: 4a305c200a7dc57aced0e0ef4ddbede79d758db3 + RNFBFunctions: fdbc59756b83ffd7426905a438071325c3ec39f9 + RNFBInAppMessaging: c6a184af904d87614cec5952ff60fdc049200b0b + RNFBInstallations: a5ace09d4c76c0e1b929cc4650630bdefd7b3c96 + RNFBMessaging: 53d0daa2ead6a94d0ae4bdb37bdbebd4ff26fcdd + RNFBML: 37c38ac8bff7a8d954b1ac9cc1a3685b9bfa227c + RNFBPerf: 859dbab3d443d38a08e28150a0c2ee612332a685 + RNFBRemoteConfig: abb2609ebc25f2a2bdb3659ce88e4697985492be + RNFBStorage: fb85a107606e14f9d9fc919f6f52b9f3e717c79a SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 3bb1ee33b5133befbd33872601fa46efdd48e841 + Yoga: 66a9fd80007d5d5fce19d1676ce17b4d5e16e9b1 PODFILE CHECKSUM: 3abe8cfe7b06f24b788e90bea320d8ae6ea6d11a diff --git a/tests/local-tests/functions/https-callable.tsx b/tests/local-tests/functions/https-callable.tsx new file mode 100644 index 0000000000..899f438300 --- /dev/null +++ b/tests/local-tests/functions/https-callable.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { Button, Text, View, ScrollView } from 'react-native'; + +import { getApp } from '@react-native-firebase/app'; +import { + getFunctions, + connectFunctionsEmulator, + httpsCallable, +} from '@react-native-firebase/functions'; + +const functions = getFunctions(); +connectFunctionsEmulator(functions, 'localhost', 5001); + +export function HttpsCallableTestComponent(): React.JSX.Element { + const [responseData, setResponseData] = useState(''); + + const handleCallFunction = async (): Promise => { + try { + const functionRunner = httpsCallable( + getFunctions(getApp()), + 'testFunctionDefaultRegionV2', + ); + const response = await functionRunner([1, 2, 3, 4, null, { yo: 1, nooo: null }]); + setResponseData(JSON.stringify(response.data, null, 2)); + } catch (e) { + console.error(e); + } + }; + + return ( + + + Ensure Emulator is running!! + + +