Receiving returned data from firebase callable functions - ios

I'm playing with Callable HTTPS-functions in iOS. I've created and deployed the following function:
export const generateLoginToken = functions.https.onCall((data, context) => {
const uid = data.user_id
if (!(typeof uid === 'string') || uid.length === 0) {
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with one argument "user_id" ');
}
admin.auth().createCustomToken(uid)
.then((token) => {
console.log("Did create custom token:", token)
return { text: "some_data" };
}).catch((error) => {
console.log("Error creating custom token:", error)
throw new functions.https.HttpsError('internal', 'createCustomToken(uid) has failed for some reason')
})
})
Then I call the function from my iOS-app like this:
let callParameters = ["user_id": userId]
self?.functions.httpsCallable("generateLoginToken").call(callParameters) { [weak self] (result, error) in
if let localError = self?.makeCallableFunctionError(error) {
single(SingleEvent.error(localError))
} else {
print("Result", result)
print("data", result?.data)
if let text = (result?.data as? [String: Any])?["text"] as? String {
single(SingleEvent.success(text))
} else {
let error = NSError.init(domain: "CallableFunctionError", code: 3, userInfo: ["info": "didn't find custom access token in the returned result"])
single(SingleEvent.error(error))
}
}
}
I can see on the logs that the function is invoked on the server with the right parameters, but I can't seem to the get data that is being returned from the function back into the app. It seems that the result.data value is nilfor some reason, even though I return {text: "some_data"} from the cloud function. How come?

Yikes! The issue was that I forgot to return the actual promise from the cloud function. This function is working:
export const generateLoginToken = functions.https.onCall((data, context) => {
const uid = data.user_id
if (!(typeof uid === 'string') || uid.length === 0) {
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with one argument "user_id" ');
}
return admin.auth().createCustomToken(uid)
.then((token) => {
console.log("Did create custom token:", token)
return { text: "some_data" };
}).catch((error) => {
console.log("Error creating custom token:", error)
throw new functions.https.HttpsError('internal', 'createCustomToken(uid) has failed for some reason')
})
})

Related

From the two same lines, one is generating an error relative to FutureOrNull

Thos are the two function from the same class I wrote: The first has the error:
A value of type 'Future<String?>' can't be returned by the 'onError' handler because it must be assignable to 'FutureOr'.
while the second is OK and I don't understand why.
Can you tell me please?
/// Creates or update the User collection
Future<String?> _createOrUpdateUser(User user) {
final email = user.email;
final nameFromEmail =
email!.substring(0, email.indexOf('#')).replaceAll(".", " ").trim();
return FirebaseFirestore.instance.collection('users').doc(user.uid).set({
'id': user.uid,
'screenName': '',
'displayName': user.displayName ?? nameFromEmail,
'photoUrl': user.photoURL,
'bio': '',
'darkMode': false,
'email': user.email,
}).then((_) {
debugPrint("User ${user.displayName} is created");
return null;
}).catchError((e) {
debugPrint(e.toString());
return Future<String?>.error(e); // <<<<< Error here: A value of type 'Future<String?>' can't be returned by the 'onError' handler because it must be assignable to 'FutureOr<Null>'.
});
}
Future<String?> _authUser(BuildContext context, LoginData data) {
debugPrint('Name: ${data.name}, Password: ${data.password}');
return auth
.signInWithEmailAndPassword(email: data.name, password: data.password)
.then((credential) {
try {
final user = credential.user;
if (user == null) {
return 'Password does not match';
} else {
_createOrUpdateUser(user);
// == Tell the app we just signed in
Provider.of<StateModel>(context, listen: false).signedIn = user;
return null;
}
} catch (e) {
return 'User not exists';
}
}).catchError((e) {
if (e.code == 'weak-password') {
debugPrint('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
debugPrint('The account already exists for that email.');
} else {
debugPrint(e.toString());
}
return Future<String?>.error(e); // <<<<< No problem here
});
}

Rxjs BehaviorSubject error handling when used with mergemap

I have the following code
#Injectable()
export class ReceptionService {
private generalInfoDataSrc$ = new BehaviorSubject<any>(null);
public generalInfoData = this.generalInfoDataSrc$.asObservable();
setGeneralInfo(dataSrc: GeneralInfoMModal) {
this.generalInfoDataSrc$.next(dataSrc);
}
}
From my component1 I will set the above as
OnSelect(patient: any) {
let generalInfo = new GeneralInfoMModal();
generalInfo.id = patient.id;
// some other code here
// this.recepService.setGeneralInfo(generalInfo);
}
// from component2
//
ngOnInit() { getPatientDetails() }
getPatientDetails() {
this.receptionService.generalInfoData.pipe(mergeMap(response => {
if (response && response.id) {
this.loading = true;
return this.receptionService.get('User/Get' + response.id, this.sourceobj);
} else {
return of(null);
}
}), takeUntil(this.unsubscribe$)).subscribe(response => {
this.patient = response;
this.loading = false;
}, error => {
this.loading = false;
// this.utility.showMsg('An error occurred while getting user.')
}, () => {
})
}
Every things works well. I keep on selecting a user thereby calling the User/Get api. But if in case if the api returns an error then error part is executed after which when there is a change in behaviorsubject(user is selected) it doesn't call the User/Get. Is there other way of handling errors with behaviorsubject or any other approach to handle the idea. How a behaviorsubject should be used in such a case.
If you are using the same behavior subject over and over again, and if there is an error, you need to set the behavior subject back to null, so that when the next user is set, it will get the latest value.
Try something like this:
getPatientDetails() {
this.receptionService.generalInfoData.pipe(mergeMap(response => {
if (response && response.id) {
this.loading = true;
return this.receptionService.get('User/Get' + response.id, this.sourceobj);
} else {
return of(null);
}
}), takeUntil(this.unsubscribe$)).subscribe(response => {
this.patient = response;
this.loading = false;
}, error => {
this.loading = false;
///////////////////////////////// ADD THIS LINE ///////////////////////
this.recepService.setGeneralInfo(null);
// this.utility.showMsg('An error occurred while getting user.')
}, () => {
})

Susbcribe to many promises?

I'm new to angular and I'm facing a problem where I need to call several promises and get all their results prior to continue the process.
// Let's assume this array is already populated
objects: any[];
// DB calls
insertObject(obj1: any): Promise<any> {
return this.insertDB('/create.json', obj1);
}
updateObject(obj: any): Promise<any> {
return this.updateDB('/update.json', obj);
}
// UI invokes this:
save(): void {
this.insertObject(objects[0])
.then((result) => {
console.log(result.data[0].id);
})
.catch((reason) => {
console.debug("[insert] error", reason);
});
this.insertObject(objects[1])
.then((result) => {
console.log(result.data[0].id);
})
.catch((reason) => {
console.debug("[insert] error", reason);
});
this.updateObject(objects[1])
.then((result) => {
console.log(result.data[0].status);
})
.catch((reason) => {
console.debug("[update] error", reason);
});
//I need to catch these 3 results in order to perform the next action.
}
Any ideas on how to achieve that?
Using the syntax Promise.all(iterable), you can execute an array of Promises. This method resolves when all promises have resolved and fails if any of those promises fail.
This can help you
let firstPromise = Promise.resolve(10);
let secondPromise = Promise.resolve(5);
let thirdPromise = Promise.resolve(20);
Promise
.all([firstPromise, secondPromise, thirdPromise])
.then(values => {
console.log(values);
});

Firebase Cloud Function Always Returns Null

I'm trying to use the following function to interact with the Stripe API - it seems to work fine as the key object is logged correctly in the console and appears as expected.
exports.createEphemeralKey = functions.https.onCall((data,context) => {
const stripe_version = data.api_version;
const customerId = data.customer_id;
if (!stripe_version) {
throw new functions.https.HttpsError('missing-stripe-version','The stripe version has not been provided.');
}
if (customerId.length === 0) {
throw new functions.https.HttpsError('missing-customerID','The customer ID has not been provided.');
}
return stripe.ephemeralKeys.create(
{customer: customerId},
{stripe_version: stripe_version}
).then((key) => {
console.log(key);
return key;
}).catch((err) => {
console.log(err);
throw new functions.https.HttpsError('stripe-error', err);
});
});
However, when I call this function from my Swift iOS App, the result?.data is always nil.
let funcParams = ["api_version": apiVersion, "customer_id": "......"]
functions.httpsCallable("createEphemeralKey").call(funcParams) { (result, error) in
if let error = error as NSError? {
print(error)
completion(nil,error)
}
if let json = result?.data as? [String: AnyObject] {
print("success")
print(json)
completion(json, nil)
} else {
print("fail")
print(result?.data)
}
}
It is probably because you don't return "data that can be JSON encoded" (see the doc). The following should work:
....
).then((key) => {
console.log(key);
return {key: key};
})
....

Localise notifications sent from Firebase

Is there a way to translate / localise notifications sent from Firebase?
I have my app setup to receive notifications successfully:
extension AppDelegate: UNUserNotificationCenterDelegate {
func setupNotifications() {
registerForRemoteNotifications()
setupNotificationTokenRefresh()
}
func registerForRemoteNotifications() {
let application = UIApplication.shared
UNUserNotificationCenter.current().delegate = self
application.registerForRemoteNotifications()
}
func setupNotificationTokenRefresh() {
storeNotificationToken()
_ = NotificationCenter.default.addObserver(
forName: .MessagingRegistrationTokenRefreshed,
object: nil,
queue: .current
) { [weak self] _ in
self?.storeNotificationToken()
}
}
private func storeNotificationToken() {
Messaging.messaging().token { [weak self] token, error in
if let error = error {
Log.error("Error fetching FCM registration token: \(error)")
} else if let token = token {
// save token
}
}
}
}
A payload is sent from a Firebase cloud function with a data object and I would like to access this data object and translate/localize the message sent.
I looked into several methods provided, but they seem to be about intercepting notifications only when the app is in the foreground, which is not what I am interested in.
The payload sent from the server:
const payload = {
notification: {
title: 'Friend request',
body: senderName + ' wants to add you as a friend'
},
data: {
senderUserId: friendRequestFrom,
type: 'friendRequest'
}
}
Because you are already using cloud functions, one way to accomplish that is to do the translation server-side with the Google Cloud Translation API. There is a good sample demonstrating how to do so with Node.js.
Say for example you are sending the notifications when new objects get added to the /notifications path in your real-time database. You can do something like this:
const Translate = require('#google-cloud/translate')
const functions = require('firebase-functions')
const projectId = 'YOUR_PROJECT_ID'
const translate = new Translate({
projectId: projectId,
});
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
exports.sendNotification = functions.database.ref(`/notifications/{notif_id}`)
.onWrite(event => {
const notification = event.data.val()
// Don't send when this isn't a new notification.
if (event.data.previous.exists()) {
return null
}
const user_id = notification.user_id
getLocalLanguageOfUser(user_id).then(language => {
if (language != 'en')
translateTo(language, notification).then(localised => {
return send(localised)
}).catch(err => {
return console.log(err)
})
} else { // it's English - no need to translate
return send(notification)
}
})
})
function getLocalLanguageOfUser(user_id) {
return new Promise((resolve, reject) => {
// default will be 'en' for English
firebase.database().ref(`users/${user_id}/language`)
.once('value').then(snapshot => {
resolve(snapshot.val() || 'en')
})
.catch(err => reject(err))
})
}
function translateTo(language, notification) {
return new Promise((resolve, reject) => {
const text = notification.text;
translate.translate(text, language).then(results => {
const translation = results[0];
resolve({
...notification,
text: translation
})
}).catch(err => reject(err))
})
}
function send(notification) {
// use your current setup to send the notification.
// 'text' key will already be localised.
}
Instead of body in your payload use body_loc_key and put the string in your application resourceId.
In onMessageRecieved:
String theKeyFromPayload= remotemessage.getNotification.getBodyLocalizationKey()
String resourceAppStatusString=theKeyFromPayload
Int resourceId= getResourceId(resourceAppStatusString, "string", this.getPackageName()
String finalBody = getResource(). getResourceName(resourceId);
finalBody is passed into the notification in your application.

Resources