Skip to content

feat(functions): turbo modules implementation #8603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 81 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
3dfaa1c
feat(storage): move to turbomodules
russellwheatley Jul 2, 2025
9d896d2
chore: convert to TS
russellwheatley Jul 2, 2025
b80b1a7
format
russellwheatley Jul 2, 2025
1431fbf
chore: ignore some things for now
russellwheatley Jul 2, 2025
cff1c5f
chore: update package.json main & types
russellwheatley Jul 2, 2025
abd0073
init with bob builder
russellwheatley Jul 2, 2025
9f30fa8
yarn.lock
russellwheatley Jul 2, 2025
18c0fdf
chore: rm rootDir from tsconfig.json
russellwheatley Jul 2, 2025
800bf1a
test: import as type for tests
russellwheatley Jul 2, 2025
1962777
chore: fix types
russellwheatley Jul 2, 2025
4dd309f
update types
russellwheatley Jul 2, 2025
193aa9b
rm type test
russellwheatley Jul 2, 2025
a49cf27
chore: fix path of type definitions
russellwheatley Jul 2, 2025
6b52129
chore: moving functions to TS
russellwheatley Jul 2, 2025
1214b3e
fix: imports
russellwheatley Jul 3, 2025
34138fa
fix some types
russellwheatley Jul 3, 2025
6802209
update types
russellwheatley Jul 3, 2025
a1cd58a
port over remaining types and delete js files
russellwheatley Jul 3, 2025
bd27a05
remove functions declaration
russellwheatley Jul 3, 2025
3b645b1
chore: create spec for turbomodules
russellwheatley Jul 3, 2025
909b963
rename spec
russellwheatley Jul 3, 2025
98f387c
rm old version of cli
russellwheatley Jul 7, 2025
7bad518
fix: need react-native as dependency for codegen to work
russellwheatley Jul 7, 2025
3ba3a76
update scripts for generating code
russellwheatley Jul 7, 2025
d3ca750
generated code for android & ios
russellwheatley Jul 7, 2025
7eda482
update podspec & config file as per RN docs
russellwheatley Jul 7, 2025
e714ad8
rm generated code
russellwheatley Jul 7, 2025
9f17910
rename to Native prefix otherwise ignored by codegen
russellwheatley Jul 7, 2025
d8ffb33
updated generic types for spec
russellwheatley Jul 7, 2025
4bd4607
latest generated code based on updated spec
russellwheatley Jul 7, 2025
90bbbed
update naming of generated code
russellwheatley Jul 7, 2025
938d1c3
update RNFB functions module
russellwheatley Jul 7, 2025
d4b2541
make file `.mm` & update podspec for functions
russellwheatley Jul 8, 2025
fba772e
podfile.lock
russellwheatley Jul 8, 2025
88eeda8
Merge branch 'main' into functions-turbo
russellwheatley Jul 8, 2025
3218d4d
yarn.lock
russellwheatley Jul 8, 2025
30f5d35
chore: update version
russellwheatley Jul 8, 2025
cdfee94
chore: reverse order of prepare script
russellwheatley Jul 8, 2025
a04eb09
feat(android): make NativeFunctionsModule compatible with existing code
russellwheatley Jul 8, 2025
1ca5cbb
update path
russellwheatley Jul 9, 2025
1ca1da2
move modular/namespaced to same directory to fix TS
russellwheatley Jul 9, 2025
f4014d0
exclude jni from compilation
russellwheatley Jul 9, 2025
459f936
fix: retreive if turbomodule
russellwheatley Jul 16, 2025
471b285
fix: rename native module name
russellwheatley Jul 16, 2025
1bb2088
setup example app
russellwheatley Jul 16, 2025
72ce7a7
update functions spec
russellwheatley Jul 16, 2025
468cdce
regenerated code with args
russellwheatley Jul 16, 2025
6884aee
update android code handling
russellwheatley Jul 16, 2025
65a9dd4
fix: iOS turbomodules implementation
russellwheatley Jul 17, 2025
9b64eac
chore: regenerate code
russellwheatley Jul 17, 2025
b966ffe
attempt at fixing iOS issue
russellwheatley Jul 17, 2025
e75a23f
Merge branch 'main' into functions-turbo
russellwheatley Jul 17, 2025
c01d1ed
yarn.lock
russellwheatley Jul 17, 2025
e657017
podlock
russellwheatley Jul 17, 2025
0b9a679
fix: ios
russellwheatley Jul 17, 2025
771e7cb
fix: ios correct types
russellwheatley Jul 17, 2025
831f2fb
chore: config for turbomodules
russellwheatley Jul 18, 2025
2500468
fix: ios implementation
russellwheatley Jul 18, 2025
3012669
chore: move types
russellwheatley Jul 18, 2025
5751dee
chore: move types back
russellwheatley Jul 18, 2025
184450e
chore: fix lint
russellwheatley Jul 18, 2025
c6a1182
fix: use get for TurboModules
russellwheatley Jul 18, 2025
3a2d634
fix: move back types
russellwheatley Jul 18, 2025
434393e
chore: remove unused js file
russellwheatley Jul 18, 2025
a661db6
chore: finally fix types
russellwheatley Jul 18, 2025
4b8c892
update to typescript
russellwheatley Jul 18, 2025
b8b969d
try updating tsconfig.json
russellwheatley Jul 18, 2025
3380bc9
fix(ios): pass nsnull if no data
russellwheatley Jul 21, 2025
9437462
chore: make var for allArgs, easier to debug
russellwheatley Jul 21, 2025
50bda8a
chore: remove unneeded package
russellwheatley Jul 22, 2025
44c9206
chore: make note about podspec updates
russellwheatley Jul 22, 2025
bcc522f
chore: update android code
russellwheatley Jul 22, 2025
fe6816b
chore: reinsert code comments
russellwheatley Jul 22, 2025
b8c7db7
chore: remove todo
russellwheatley Jul 22, 2025
67cce20
chore: typing improvement
russellwheatley Jul 22, 2025
c8b2683
chore: types
russellwheatley Jul 22, 2025
cf18e11
chore: improve details typing
russellwheatley Jul 22, 2025
699c96b
chore: we now expose everything via index (now modular)
russellwheatley Jul 22, 2025
3bf85d8
chore: improve web typing
russellwheatley Jul 22, 2025
c83c945
yarn.lock
russellwheatley Jul 22, 2025
ba19d1b
fix: allow details to be null
russellwheatley Jul 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default [
'**/type-test.ts',
'packages/**/modular/dist/**/*',
'packages/vertexai/__tests__/test-utils',
'packages/vertexai/dist',
'packages/**/dist',
],
},
...compat
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"lerna:clean": "lerna clean",
"build:all:clean": "lerna run build:clean",
"build:all:build": "lerna run 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))",
Expand Down Expand Up @@ -69,6 +70,7 @@
"@firebase/rules-unit-testing": "^4.0.1",
"@inquirer/prompts": "^7.4.1",
"@octokit/core": "^6.1.5",
"@react-native-community/cli": "latest",
"@tsconfig/node-lts": "^22.0.1",
"@types/react": "^19.0.0",
"@types/react-native": "^0.73.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -43,7 +43,7 @@ public Context getApplicationContext() {
return getContext().getApplicationContext();
}

protected ExecutorService getExecutor() {
public ExecutorService getExecutor() {
return executorService.getExecutor();
}

Expand Down
3 changes: 2 additions & 1 deletion packages/app/lib/internal/nativeModuleAndroidIos.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/app/lib/internal/registry/nativeModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ function nativeModuleKey(module) {
*/
function nativeModuleMethodWrapped(namespace, method, argToPrepend) {
return (...args) => {
const possiblePromise = method(...[...argToPrepend, ...args]);
const allArgs = [...argToPrepend, ...args];
const possiblePromise = method(...allArgs);

if (possiblePromise && possiblePromise.then) {
const jsStack = new Error().stack;
Expand Down
4 changes: 3 additions & 1 deletion packages/app/lib/modular/index.d.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
11 changes: 10 additions & 1 deletion packages/functions/RNFBFunctions.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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}'"
Expand Down
7 changes: 4 additions & 3 deletions packages/functions/__tests__/functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it, jest } from '@jest/globals';

import functions, {
import {
firebase,
getFunctions,
connectFunctionsEmulator,
Expand All @@ -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';
Expand Down
4 changes: 3 additions & 1 deletion packages/functions/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ android {
}
}

sourceSets {
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/reactnative/java']
// Exclude generated JNI files from compilation
java.excludes = ['**/generated/jni/**']
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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.NativeFunctionsModuleSpec;
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 NativeFunctionsModule extends NativeFunctionsModuleSpec {
private static final String SERVICE_NAME = "Functions";
private final UniversalFirebaseFunctionsModule module;
private final UniversalFirebaseModule universalFirebaseModule;

public NativeFunctionsModule(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<Object> 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<Object> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<NativeModule> createNativeModules(@Nonnull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new NativeFunctionsModule(reactContext));
return modules;
}

@Nonnull
@Override
public List<ViewManager> createViewManagers(@Nonnull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
@@ -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 NativeFunctionsModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "NativeFunctionsModule";

public NativeFunctionsModuleSpec(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);
}
Loading
Loading