Skip to content

Commit

Permalink
Implement client-side Firebase Cloud Functions locations (#23)
Browse files Browse the repository at this point in the history
* Implement client-side Firebase Cloud Functions locations

Allows client-side Functions calls to remote functions using a supported 
Region (Android & iOS) or Custom Domain (iOS only).

See 
https://firebase.google.com/docs/functions/locations#client-side_location_selection_for_callable_functions

Adds a method to FirebaseApp ('functions') to set Region or 
CustomDomain. 

Example usage: firebase().app().functions("europe-west")

See 
https://firebase.google.com/docs/reference/node/firebase.app.App#functions

Example client-side call:
firebase().functions().httpsCallable('functionName')()
calls Cloud Function:
exports functionName = functions.region('europe-west1')...
See 
https://firebase.google.com/docs/functions/locations#best_practices_for_changing_region

Supported regions: See 
https://firebase.google.com/docs/functions/locations

By default functions run in the "us-central1" region, unless another 
region is selected.

Android Functions Emulator: Set 'localhost' host to 10.0.2.2

* Update README.md file with usage instructions

* Update README.md file with usage instructions

* Clenaup README.md text
  • Loading branch information
Langers8 authored Dec 31, 2021
1 parent 53bcff4 commit b01b075
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 8 deletions.
35 changes: 35 additions & 0 deletions packages/firebase-functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,41 @@ firebase()
});
```

## Regional Cloud Functions
Cloud Functions are _regional_, which means the infrastructure that runs your Cloud Function is located in specific regions.

By default, functions run in the _us-central1_ region. View the [supported regions](https://firebase.google.com/docs/functions/locations).

To run functions in a different region, after initializing Firebase App set the region using _firebase().app().functions(region)_.

Regional function endpoint example (using _europe-west2_ region ):
```ts
// Deployed HTTPS callable
exports.listProducts = functions.region("europe-west2").https.onCall(() => {
return [
/* ... */
// Return some data
];
});
```

To access the regional function endpoint:
```ts
import { firebase } from '@nativescript/firebase-core';
import '@nativescript/firebase-functions';

firebase().initializeApp();
firebase().app().functions("europe-west2");

firebase()
.functions()
.httpsCallable('listProducts')()
.then((response) => {
setProducts(response.data);
setLoading(false);
});
```

## Using an emulator

Whilst developing your application with Cloud Functions, it is possible to run the functions inside of a local emulator.
Expand Down
40 changes: 35 additions & 5 deletions packages/firebase-functions/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import {
HttpsCallable,
HttpsCallableOptions,
HttpsErrorCode,
IFunctions,
IFunctions
} from "./common";
import {deserialize, firebase, FirebaseApp, serialize} from "@nativescript/firebase-core";


let defaultFunctions: Functions;

const fb = firebase();
Expand All @@ -24,6 +23,31 @@ Object.defineProperty(fb, 'functions', {
},
writable: false,
});
/**
Firebase Functions Region - Region for which to run HttpsCallable method
Set parameter using firebase().app().functions(regionOrCustomDomain: string)
@see https://firebase.google.com/docs/reference/android/com/google/firebase/functions/FirebaseFunctions
@note If not set, default region is used ("us-central1")
*/
let defaultRegionOrCustomDomain: string;
/**
Add 'functions' method to FirebaseApp class
@see https://firebase.google.com/docs/reference/node/firebase.app.App
@see https://firebase.google.com/docs/functions/locations
@param regionOrCustomDomain(string)(Optional): Name of the region for which to Functions results
@return Functions
*/
const fbApp = FirebaseApp;
Object.defineProperty(fbApp.prototype, 'functions', {
value: (regionOrCustomDomain?: string) => {
defaultRegionOrCustomDomain = regionOrCustomDomain;
if (!defaultFunctions) {
defaultFunctions = new Functions();
}
return defaultFunctions;
},
writable: false,
});


function errorToCode(error: com.google.firebase.functions.FirebaseFunctionsException.Code) {
Expand Down Expand Up @@ -130,13 +154,19 @@ export class Functions implements IFunctions {

constructor(app?: FirebaseApp) {
if (app?.native) {
this.#native = com.google.firebase.functions.FirebaseFunctions.getInstance(app.native);
this.#native = defaultRegionOrCustomDomain
? com.google.firebase.functions.FirebaseFunctions.getInstance(app.native, defaultRegionOrCustomDomain)
: com.google.firebase.functions.FirebaseFunctions.getInstance(app.native);
} else {
if(defaultFunctions){
return defaultFunctions;
}
defaultFunctions = this;
this.#native = com.google.firebase.functions.FirebaseFunctions.getInstance();
// If defaultRegionOrCustomDomain is set, get FirebaseFunctions instance using that parameter
// @see https://firebase.google.com/docs/functions/locations#client-side_location_selection_for_callable_functions
this.#native = defaultRegionOrCustomDomain
? com.google.firebase.functions.FirebaseFunctions.getInstance(defaultRegionOrCustomDomain)
: com.google.firebase.functions.FirebaseFunctions.getInstance();
}
}

Expand All @@ -162,7 +192,7 @@ export class Functions implements IFunctions {
}

useEmulator(host: string, port: number) {
this.native.useEmulator(host, port);
this.native.useEmulator(host === 'localhost' ? '10.0.2.2' : host, port);
}

get native() {
Expand Down
13 changes: 13 additions & 0 deletions packages/firebase-functions/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,22 @@ export declare class Functions implements IFunctions {
}

declare module '@nativescript/firebase-core' {

export interface Firebase extends FirebaseFunctions {}
// Add 'functions' method to FirebaseApp
export interface FirebaseApp extends FirebaseFunctionsApp {}
}

export interface FirebaseFunctions {
static functions(app?: FirebaseApp): Functions;
}
/**
Add Region (Android & iOS) or Custom Domain (iOS only) to Firebase Functions HTTPS call
@param regionOrCustomDomain (string) (optional): Region (Android or iOS) or Custom Domain (iOS only)
@return Functions
@see Supported Regions: https://firebase.google.com/docs/functions/locations
@example firebase().app().functions("us-central1")
*/
export interface FirebaseFunctionsApp {
static functions(regionOrCustomDomain?: string): Functions;
}
67 changes: 64 additions & 3 deletions packages/firebase-functions/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
HttpsCallable,
HttpsCallableOptions,
HttpsErrorCode,
IFunctions,
IFunctions
} from "./common";
import {deserialize, firebase, FirebaseApp, serialize} from "@nativescript/firebase-core";

Expand All @@ -23,6 +23,34 @@ Object.defineProperty(fb, 'functions', {
},
writable: false,
});
/**
Firebase Functions Region - Region for which to run HttpsCallable method
Set parameter using firebase().app().functions(regionOrCustomDomain: string)
@link https://firebase.google.com/docs/reference/node/firebase.app.App
@link https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions
@note If not set, default region is used ('us-central1')
*/
let defaultRegionOrCustomDomain: string;
/**
Add 'functions' method to FirebaseApp class
@param regionOrCustomDomain(string)(Optional): Name of the Region or Custom Domain for which to Functions results
@return Functions
@see FirebaseFunctions
@link https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions
@see Supported Regions
@see https://firebase.google.com/docs/functions/locations
*/
const fbApp = FirebaseApp;
Object.defineProperty(fbApp.prototype, 'functions', {
value: (regionOrCustomDomain?: string) => {
defaultRegionOrCustomDomain = regionOrCustomDomain;
if (!defaultFunctions) {
defaultFunctions = new Functions();
}
return defaultFunctions;
},
writable: false,
});

function errorToCode(error: NSError) {
let code = HttpsErrorCode.UNKNOWN;
Expand Down Expand Up @@ -115,13 +143,29 @@ export class Functions implements IFunctions {

constructor(app?: FirebaseApp) {
if (app?.native) {
this.#native = FIRFunctions.functionsForApp(app.native);
if(defaultRegionOrCustomDomain){
this.#native = isRegion(defaultRegionOrCustomDomain) // Check whether a Region has been set
? FIRFunctions.functionsForAppRegion(app.native, defaultRegionOrCustomDomain) // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functionsforapp:region:
: isCustomDomain(defaultRegionOrCustomDomain) // Check whether using a Custom Domain has been set
? FIRFunctions.functionsForAppCustomDomain(app.native, defaultRegionOrCustomDomain) // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functionsforapp:customdomain:
: FIRFunctions.functionsForApp(app.native); // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functionsforapp:
} else {
this.#native = FIRFunctions.functionsForApp(app.native); // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functionsforapp:
}
} else {
if(defaultFunctions){
return defaultFunctions;
}
defaultFunctions = this;
this.#native = FIRFunctions.functions();
if(defaultRegionOrCustomDomain){
this.#native = isRegion(defaultRegionOrCustomDomain) // Check whether a Region has been set
? FIRFunctions.functionsForRegion(defaultRegionOrCustomDomain) // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functionsforregion:
: isCustomDomain(defaultRegionOrCustomDomain) // Check whether using a Custom Domain has been set
? FIRFunctions.functionsForCustomDomain(defaultRegionOrCustomDomain) // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functionsforcustomdomain:
: FIRFunctions.functions(); // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functions
} else {
this.#native = FIRFunctions.functions(); // @see https://firebase.google.com/docs/reference/ios/firebasefunctions/api/reference/Classes/FIRFunctions#+functions
}
}
}

Expand Down Expand Up @@ -178,3 +222,20 @@ export class Functions implements IFunctions {
return this.#app;
}
}
/**
Check whether a regionOrCustomDomain string is a Region for the http trigger, such as “us-central1”.
@param regionOrCustomDomain(string): Text to parse
@return boolean: TRUE if a Region; FALSE if not
*/
function isRegion(regionOrCustomDomain: string): boolean{
const elems = regionOrCustomDomain.split('.');
return elems.length === 1 ? true : false;
}
/**
Check whether a regionOrCustomDomain string is a Custom Domain for the http trigger, such as “https://mydomain.com”
@param regionOrCustomDomain(string): Text to parse
@return boolean: TRUE if a CustomDomain; FALSE if not
*/
function isCustomDomain(regionOrCustomDomain: string): boolean{
return !isRegion(regionOrCustomDomain);
}

0 comments on commit b01b075

Please sign in to comment.