Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3d6358a
feat(cloud_functions): httpsCallable.stream support
MichaelVerdon Dec 9, 2025
f20f329
feat: android impl
MichaelVerdon Dec 10, 2025
87b000a
feat: typescript impl
MichaelVerdon Dec 11, 2025
b5aa190
feat: turbomodules impl and use correct native method for android
MichaelVerdon Dec 11, 2025
2024041
feat: tests and event emission working
MichaelVerdon Dec 11, 2025
fe37b79
fix: tests
MichaelVerdon Dec 11, 2025
ff8c3b1
feat: definition tests
MichaelVerdon Dec 12, 2025
9c44d18
feat: iOS impl
MichaelVerdon Dec 12, 2025
bb15bf4
fix: ios stream
MichaelVerdon Dec 12, 2025
3560577
fix: jest
MichaelVerdon Dec 15, 2025
1445156
feat: web impl
MichaelVerdon Dec 15, 2025
d2b94d1
feat: error handling
MichaelVerdon Dec 15, 2025
0cfed52
format: js
MichaelVerdon Dec 15, 2025
bcf8869
fix: ts defs
MichaelVerdon Dec 15, 2025
a4e497d
fix: ts ignore
MichaelVerdon Dec 15, 2025
6c0492f
fix: lint
MichaelVerdon Dec 15, 2025
7ef1474
format: java format
MichaelVerdon Dec 15, 2025
5c2c5f3
format: indentations
MichaelVerdon Dec 15, 2025
65e5a5a
format: indentation
MichaelVerdon Dec 15, 2025
9245d6d
format: clang
MichaelVerdon Dec 15, 2025
14967b1
Merge branch 'main' into cloud-functions-streaming
MichaelVerdon Dec 18, 2025
13e6b2f
fix: readd types from merge conflict fixes
MichaelVerdon Dec 19, 2025
b500b8d
fix(web): web types
MichaelVerdon Dec 19, 2025
b612452
fix: more web types
MichaelVerdon Dec 19, 2025
0544303
fix: stream type
MichaelVerdon Dec 19, 2025
6c81f40
feat: util mock func
MichaelVerdon Dec 19, 2025
856e3b9
fix: mocking
MichaelVerdon Dec 19, 2025
b25f207
chore: format
MichaelVerdon Dec 19, 2025
b89038f
fix: android impl finished and web refactoring
MichaelVerdon Dec 29, 2025
3d36554
Merge branch 'main' into cloud-functions-streaming
MichaelVerdon Dec 29, 2025
66e973e
fix: define swift version in podspec
MichaelVerdon Dec 29, 2025
fc036de
chore: ignore clang error
MichaelVerdon Dec 29, 2025
bd32b12
feat: add error logging
MichaelVerdon Dec 29, 2025
69ecdb6
fix: build issues
MichaelVerdon Dec 30, 2025
bfc3f18
Merge branch 'main' into cloud-functions-streaming
MichaelVerdon Dec 30, 2025
bd31336
chore: remove unneccessary timeout
MichaelVerdon Dec 30, 2025
58302d6
fix: release mode build isuse
MichaelVerdon Dec 30, 2025
4069444
fix: ios flags
MichaelVerdon Dec 30, 2025
3ba82f5
fix: android implementation passing locally
MichaelVerdon Dec 30, 2025
edb93b5
fix: compiler flags
MichaelVerdon Dec 31, 2025
6470b1d
fix: podspec
MichaelVerdon Dec 31, 2025
4ffa0f6
fix: podspec
MichaelVerdon Dec 31, 2025
0c9488a
format: java
MichaelVerdon Dec 31, 2025
20a4980
fix: jest tests
MichaelVerdon Dec 31, 2025
988d28c
format: js
MichaelVerdon Dec 31, 2025
cdc0e9c
fix: prevent timeouts on other platforms
MichaelVerdon Dec 31, 2025
db22a5d
fix: setnativemodule
MichaelVerdon Jan 2, 2026
a1ce057
fix: compiler flags
MichaelVerdon Jan 2, 2026
920c424
fix: undo podfile changes in tests/
MichaelVerdon Jan 2, 2026
48ebc77
fix: swift compilation
MichaelVerdon Jan 2, 2026
ab1c6f5
fix: listener funcs (other)
MichaelVerdon Jan 5, 2026
c9fab0b
fix: check before storage listener removal
MichaelVerdon Jan 5, 2026
ae0a742
fix: mac issues listener removal
MichaelVerdon Jan 5, 2026
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
8 changes: 8 additions & 0 deletions .github/workflows/scripts/functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ export { fetchAppCheckTokenV2 } from './fetchAppCheckToken';
export { sendFCM } from './sendFCM';

export { testFetchStream, testFetch } from './vertexaiFunctions';

export {
testStreamingCallable,
testProgressStream,
testComplexDataStream,
testStreamWithError,
testStreamResponse,
} from './testStreamingCallable';
205 changes: 205 additions & 0 deletions .github/workflows/scripts/functions/src/testStreamingCallable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { onCall, CallableRequest, CallableResponse } from 'firebase-functions/v2/https';
import { logger } from 'firebase-functions/v2';

/**
* Test streaming callable function that sends multiple chunks of data
* This function demonstrates Server-Sent Events (SSE) streaming
*/
export const testStreamingCallable = onCall(
async (
req: CallableRequest<{ count?: number; delay?: number }>,
response?: CallableResponse<any>,
) => {
const count = req.data.count || 5;
const delay = req.data.delay || 500;

logger.info('testStreamingCallable called', { count, delay });

// Send chunks of data over time
for (let i = 0; i < count; i++) {
// Wait for the specified delay
await new Promise(resolve => setTimeout(resolve, delay));

if (response) {
await response.sendChunk({
index: i,
message: `Chunk ${i + 1} of ${count}`,
timestamp: new Date().toISOString(),
data: {
value: i * 10,
isEven: i % 2 === 0,
},
});
}
}

// Return final result
return { totalCount: count, message: 'Stream complete' };
},
);

/**
* Test streaming callable that sends progressive updates
*/
export const testProgressStream = onCall(
async (
req: CallableRequest<{ task?: string }>,
response?: CallableResponse<any>,
) => {
const task = req.data.task || 'Processing';

logger.info('testProgressStream called', { task });

const updates = [
{ progress: 0, status: 'Starting...', task },
{ progress: 25, status: 'Loading data...', task },
{ progress: 50, status: 'Processing data...', task },
{ progress: 75, status: 'Finalizing...', task },
{ progress: 100, status: 'Complete!', task },
];

for (const update of updates) {
await new Promise(resolve => setTimeout(resolve, 300));
if (response) {
await response.sendChunk(update);
}
}

return { success: true };
},
);

/**
* Test streaming with complex data types
*/
export const testComplexDataStream = onCall(
async (req: CallableRequest, response?: CallableResponse<any>) => {
logger.info('testComplexDataStream called');

const items = [
{
id: 1,
name: 'Item One',
tags: ['test', 'streaming', 'firebase'],
metadata: {
created: new Date().toISOString(),
version: '1.0.0',
},
},
{
id: 2,
name: 'Item Two',
tags: ['react-native', 'functions'],
metadata: {
created: new Date().toISOString(),
version: '1.0.1',
},
},
{
id: 3,
name: 'Item Three',
tags: ['cloud', 'streaming'],
metadata: {
created: new Date().toISOString(),
version: '2.0.0',
},
},
];

// Stream each item individually
for (const item of items) {
await new Promise(resolve => setTimeout(resolve, 200));
if (response) {
await response.sendChunk(item);
}
}

// Return summary
return {
summary: {
totalItems: items.length,
processedAt: new Date().toISOString(),
},
};
},
);

/**
* Test streaming with error handling
*/
export const testStreamWithError = onCall(
async (
req: CallableRequest<{ shouldError?: boolean; errorAfter?: number }>,
response?: CallableResponse<any>,
) => {
const shouldError = req.data.shouldError !== false;
const errorAfter = req.data.errorAfter || 2;

logger.info('testStreamWithError called', { shouldError, errorAfter });

for (let i = 0; i < 5; i++) {
if (shouldError && i === errorAfter) {
throw new Error('Simulated streaming error after chunk ' + errorAfter);
}

await new Promise(resolve => setTimeout(resolve, 300));
if (response) {
await response.sendChunk({
chunk: i,
message: `Processing chunk ${i + 1}`,
});
}
}

return {
success: true,
message: 'All chunks processed successfully',
};
},
);

/**
* Test streaming callable that returns the type of data sent
* Similar to Dart's testStreamResponse - sends back the type of input data
*/
export const testStreamResponse = onCall(
async (req: CallableRequest<any>, response?: CallableResponse<any>) => {
logger.info('testStreamResponse called', { data: req.data });

// Determine the type of the input data
let partialData: string;
if (req.data === null || req.data === undefined) {
partialData = 'null';
} else if (typeof req.data === 'string') {
partialData = 'string';
} else if (typeof req.data === 'number') {
partialData = 'number';
} else if (typeof req.data === 'boolean') {
partialData = 'boolean';
} else if (Array.isArray(req.data)) {
partialData = 'array';
} else if (typeof req.data === 'object') {
// For deep maps, check if it has the expected structure
if (req.data.type === 'deepMap' && req.data.inputData) {
partialData = req.data.inputData;
} else {
partialData = 'object';
}
} else {
partialData = 'unknown';
}

// Send chunk with the type information
if (response) {
await response.sendChunk({
partialData,
});
}

// Return final result
return {
partialData,
type: typeof req.data,
};
},
);
10 changes: 10 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ jest.doMock('react-native', () => {
checkForUpdate: jest.fn(),
signOutTester: jest.fn(),
},
RNFBFunctionsModule: {
httpsCallableStream: jest.fn(),
httpsCallableStreamFromUrl: jest.fn(),
removeFunctionsStreaming: jest.fn(),
},
NativeRNFBTurboFunctions: {
httpsCallableStream: jest.fn(),
httpsCallableStreamFromUrl: jest.fn(),
removeFunctionsStreaming: jest.fn(),
},
RNFBCrashlyticsModule: {
isCrashlyticsCollectionEnabled: false,
checkForUnsentReports: jest.fn(),
Expand Down
16 changes: 14 additions & 2 deletions packages/functions/RNFBFunctions.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,22 @@ 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,mm,cpp}'
s.swift_version = '5.0'
s.source_files = 'ios/**/*.{h,m,mm,cpp,swift}'
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'
s.compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1 -std=c++20'
# Swift requires modules to be enabled, so we keep CLANG_ENABLE_MODULES=YES
# Only disable explicit modules to avoid libclang.dylib warnings
# C++ standard library headers should be found via the C++20 standard setting
s.pod_target_xcconfig = {
'CLANG_CXX_LIBRARY' => 'libc++',
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20',
'CLANG_ENABLE_MODULES' => 'YES',
'CLANG_ENABLE_MODULES_EXPLICIT' => 'NO',
'OTHER_CPLUSPLUSFLAGS' => '$(inherited) -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1 -std=c++20',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}

# React Native dependencies
s.dependency 'React-Core'
Expand Down
Loading
Loading