Skip to content

Commit

Permalink
feat(other): add Functions support
Browse files Browse the repository at this point in the history
  • Loading branch information
Salakar committed Jul 9, 2024
1 parent 1b2e247 commit a752075
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 6 deletions.
89 changes: 84 additions & 5 deletions packages/functions/e2e/functions.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,78 @@
*
*/

const SAMPLE_DATA = require('../../../.github/workflows/scripts/functions/lib/sample-data').default;
// Keep this in sync with the data in:
// https://github.com/invertase/react-native-firebase/blob/main/.github/workflows/scripts/functions/src/sample-data.ts
const SAMPLE_DATA = {
number: 1234,
string: 'acde',
boolean: true,
null: null,
object: {
number: 1234,
string: 'acde',
boolean: true,
null: null,
},
array: [1234, 'acde', true, null],
deepObject: {
array: [1234, 'acde', false, null],
object: {
number: 1234,
string: 'acde',
boolean: true,
null: null,
array: [1234, 'acde', true, null],
},
number: 1234,
string: 'acde',
boolean: true,
null: null,
},
deepArray: [
1234,
'acde',
true,
null,
[1234, 'acde', true, null],
{
number: 1234,
string: 'acde',
boolean: true,
null: null,
array: [1234, 'acde', true, null],
},
],
deepMap: {
number: 123,
string: 'foo',
booleanTrue: true,
booleanFalse: false,
null: null,
list: ['1', 2, true, false],
map: {
number: 123,
string: 'foo',
booleanTrue: true,
booleanFalse: false,
null: null,
},
},
deepList: [
'1',
2,
true,
false,
['1', 2, true, false],
{
number: 123,
string: 'foo',
booleanTrue: true,
booleanFalse: false,
null: null,
},
],
};

describe('functions() modular', function () {
describe('firebase v8 compatibility', function () {
Expand Down Expand Up @@ -104,7 +175,7 @@ describe('functions() modular', function () {
describe('httpsCallableFromUrl()', function () {
it('Calls a function by URL', async function () {
let hostname = 'localhost';
if (device.getPlatform() === 'android') {
if (Platform.android) {
hostname = '10.0.2.2';
}
const functionRunner = firebase
Expand Down Expand Up @@ -322,7 +393,11 @@ describe('functions() modular', function () {
await functionRunner({ delay: 3000 });
return Promise.reject(new Error('Did not throw an Error.'));
} catch (error) {
error.message.should.containEql('DEADLINE').containEql('EXCEEDED');
if (Platform.other) {
error.message.should.containEql('deadline-exceeded');
} else {
error.message.should.containEql('DEADLINE').containEql('EXCEEDED');
}
return Promise.resolve();
}
});
Expand Down Expand Up @@ -431,7 +506,7 @@ describe('functions() modular', function () {
const { getFunctions, httpsCallableFromUrl } = functionsModular;

let hostname = 'localhost';
if (device.getPlatform() === 'android') {
if (Platform.android) {
hostname = '10.0.2.2';
}
const functions = getFunctions(firebase.app());
Expand Down Expand Up @@ -700,7 +775,11 @@ describe('functions() modular', function () {
await functionRunner({ delay: 3000 });
return Promise.reject(new Error('Did not throw an Error.'));
} catch (error) {
error.message.should.containEql('DEADLINE').containEql('EXCEEDED');
if (Platform.other) {
error.message.should.containEql('deadline-exceeded');
} else {
error.message.should.containEql('DEADLINE').containEql('EXCEEDED');
}
return Promise.resolve();
}
});
Expand Down
24 changes: 23 additions & 1 deletion packages/functions/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
} from '@react-native-firebase/app/lib/internal';
import HttpsError from './HttpsError';
import version from './version';
import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule';
import fallBackModule from './web/RNFBFunctionsModule';

const namespace = 'functions';
const nativeModuleName = 'RNFBFunctionsModule';
Expand All @@ -34,7 +36,6 @@ export {
connectFunctionsEmulator,
} from './modular/index';

// import { HttpsErrorCode } from '@react-native-firebase/functions';
export const HttpsErrorCode = {
OK: 'ok',
CANCELLED: 'cancelled',
Expand All @@ -53,6 +54,24 @@ export const HttpsErrorCode = {
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',
};

const statics = {
Expand Down Expand Up @@ -192,3 +211,6 @@ export default createModuleNamespace({
// functions().logEvent(...);
// firebase.functions().logEvent(...);
export const firebase = getFirebaseRoot();

// Register the interop module for non-native platforms.
setReactNativeModule(nativeModuleName, fallBackModule);
2 changes: 2 additions & 0 deletions packages/functions/lib/web/RNFBFunctionsModule.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// No-op for android.
export default {};
2 changes: 2 additions & 0 deletions packages/functions/lib/web/RNFBFunctionsModule.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// No-op for ios.
export default {};
126 changes: 126 additions & 0 deletions packages/functions/lib/web/RNFBFunctionsModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
getApp,
getFunctions,
httpsCallable,
httpsCallableFromURL,
connectFunctionsEmulator,
} from '@react-native-firebase/app/lib/internal/web/firebaseFunctions';

/**
* This is a 'NativeModule' for the web platform.
* Methods here are identical to the ones found in
* the native android/ios modules e.g. `@ReactMethod` annotated
* java methods on Android.
*/
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.
*/
async httpsCallable(appName, regionOrCustomDomain, host, port, name, wrapper, options) {
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.
if (regionOrCustomDomain.startsWith('http')) {
functionsInstance.customDomain = regionOrCustomDomain;
functionsInstance.region = 'us-central1';
} else {
functionsInstance.region = regionOrCustomDomain;
functionsInstance.customDomain = null;
}
} else {
functionsInstance = getFunctions(app);
functionsInstance.region = 'us-central1';
functionsInstance.customDomain = null;
}
if (host) {
connectFunctionsEmulator(functionsInstance, host, port);
// Hack to work around emulator origin not being set on the instance.
functionsInstance.emulatorOrigin = `http://${host}:${port}`;
}
let callable;
if (Object.keys(options).length) {
callable = httpsCallable(functionsInstance, name, options);
} else {
callable = httpsCallable(functionsInstance, name);
}
// if data is undefined use null,
const data = wrapper['data'] ?? null;
const result = await callable(data);
return result;
} catch (error) {
const { code, message, details } = error;
const nativeError = {
code,
message,
userInfo: {
code: code ? code.replace('functions/', '') : 'unknown',
message,
details,
},
};
return Promise.reject(nativeError);
}
},

/**
* 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.
*/
async httpsCallableFromUrl(appName, regionOrCustomDomain, host, port, url, wrapper, options) {
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.
if (regionOrCustomDomain.startsWith('http')) {
functionsInstance.customDomain = regionOrCustomDomain;
} else {
functionsInstance.region = regionOrCustomDomain;
}
} else {
functionsInstance = getFunctions(app);
functionsInstance.region = 'us-central1';
functionsInstance.customDomain = null;
}
if (host) {
connectFunctionsEmulator(functionsInstance, host, port);
// Hack to work around emulator origin not being set on the instance.
functionsInstance.emulatorOrigin = `http://${host}:${port}`;
}
const callable = httpsCallableFromURL(functionsInstance, url, options);
const result = await callable(wrapper['data']);
return result;
} catch (error) {
const { code, message, details } = error;
const nativeError = {
code,
message,
userInfo: {
code: code ? code.replace('functions/', '') : 'unknown',
message,
details,
},
};
return Promise.reject(nativeError);
}
},
};

0 comments on commit a752075

Please sign in to comment.