Parsing a FIRHTTPSCallableResult object data from Firebase Cloud Function with Swift - ios

I have this simple Cloud Function:
export const getTasks = functions.https.onRequest((request, response) => {
admin.firestore().collection('tasks').get()
.then(snapshot => {
const results = []
snapshot.forEach(task => {
const data = task.data()
results.push(data)
})
response.send(results)
})
.catch(error => {
console.log(error)
response.status(500).send(error)
})
});
The https call, from the browser, gives me a correct json:
[
{
title: "A title",
dueDate: "2018-07-03T18:33:27.537Z",
isComplete: true,
type: "task",
date: "2018-07-02T18:33:27.537Z"
},
{
type: "task",
date: "2018-07-02T18:36:25.506Z",
title: "Wowo",
dueDate: "2018-07-02T21:59:59.000Z",
isComplete: true
},
{
title: "Abc",
dueDate: "2018-07-04T18:31:58.050Z",
isComplete: false,
type: "task",
date: "2018-07-02T18:31:58.050Z"
}
]
But when I try to receive data from the iOS client through the function, I get a FIRHTTPSCallableResult object and a nil object:
functions.httpsCallable("getTasks").call() { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
//...
}
// ...
}
print( "result -> \(type(of: result))")
print( "result?.data -> \(type(of: result?.data))")
Log:
result -> Optional<FIRHTTPSCallableResult>
result?.data -> Optional<Any>
I tried to use JSON parsing but it does not work. How can I get the json?
Thanks

The API documentation for the data field states:
The data is in the form of native objects. For example, if your
trigger returned an array, this object would be an NSArray. If your
trigger returned a JavaScript object with keys and values, this object
would be an NSDictionary.
Since you're sending an array of objects from your function, you would treat the contents of data as an NSArray of NSDictionary objects.

Related

forEach() iterates too fast with ASYNC AWAIT - Skipping data

I am re-writing my website and am trying to import my members from array of JSON objects.
Each object represents a band that the user has uploaded. So I wrote a quick file that works with the list and my new database / authentication system, auth0.
As outline in the comments below...
First I Request an access token so I can create an account for each member using Auth0. Then I iterate through my array of objects representing bands. For each band object I have the code do a few things... First, it creates a new account on my auth system. This sends back a user ID. I add the user ID to the band object to be stored in my database. It adds the location as well as adds a few posts that the user may have had from the old system.
This all works well if I do one user... But if I let it iterate through the whole array it doesn't work. It added about 15 bands to the auth system and 50 bands to the database... It seems like its going too fast to successfully publish them all. Any ideas on how to fix this?
** Also tried this with a for loop instead of for each. Moving everything inside the for loop creates both the same number of bands and user accounts, but now it times out at about 10 bands instead of doing all of them.
Thanks!
const uuidv4 = require('uuid')
const BulkBands = require('./BulkBands')
const fetch = require("node-fetch");
let bandCount = 0
//First I need to get an Access Token for my Auth Proider, Auth0
const getAccessToken = async () => {
try {
let response = await fetch(`https://nm-music.auth0.com/oauth/token`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: '{"client_id":"xxxxxxxxx","client_secret":"xxxxxxxxx", "audience":"https://xxxxxxxx.com/api/v2/","grant_type":"client_credentials"}'
})
const responseData = await response.json()
//Now I start The Process of Adding the Bands.
addTheBands(responseData.access_token)
} catch (error) {
console.log(error)
}
}
//Here I use the array method, forEach() to iterate over the list of about 77 bands.
const addTheBands = (accessToken) => {
BulkBands.forEach(band => {
//The first thing I want to do for each band is create an account for them on the Auth system.
createNewAccount(band, accessToken)
})
}
//Here is the function to create the account.
const createNewAccount = async (band, accessToken) => {
try {
const response = await fetch(`https://xxxxxxxxx.com/api/v2/users`, {
method: 'POST',
headers: {
'content-type': 'application/json;',
"Authorization": `Bearer ${accessToken}`,
},
body: JSON.stringify({
"connection": 'Username-Password-Authentication',
"email": band.bandEmail,
// "phone_number": "+19995550123",
"user_metadata": {},
"blocked": false,
"email_verified": false,
// "phone_verified": false,
"app_metadata": {},
// "given_name": band.bandName,
// "family_name": band.bandName,
"name": band.bandEmail,
// "nickname": "Johnny",
// "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png",
// "user_id": "",
// "connection": "Initial-Connection",
"password": "xxxxxxxx",
"verify_email": false,
// "username": band.bandEmail
})
})
let newUserResponse = await response.json()
//Now that their account has been made, I want to add the band to my database, and use the Account ID from newUserResponse as the userId for the band. That way the band is associted with their account.
addTheBand({
quoteGenerator: [],
userId: newUserResponse.identities[0].user_id,
posts: [
{
"type": "band",
"data": "",
"date": new Date(),
"postId": uuidv4(),
"approved": null,
"rockOn": []
},
],
type: 'band',
bandName: band.bandName,
bandEmail: band.bandEmail,
bandGenre: band.bandGenre,
bandBio: band.bandBio,
youtube: [band.youtube1, band.youtube2],
published: false,
cancellationPolicy: 'You may cancel this performance up to 2 weeks prior to the performance date without penalty. If this performance is cancelled within 2 weeks of the performance date the booking party will be required to pay 50% of the booking fee to the band. If the booking is cancelled within 3 days of the performance date the full payment is required to be paid to the band. ',
baseCost: band.baseCost,
mainDate: {
charge: 0,
sunday: 0,
monday: 0,
tuesday: 0,
wednesday: 0,
thursday: 0,
friday: 0,
saturday: 0,
}
}, band)
} catch (error) {
console.log(error)
}
}
//Now to start adding the band...
const addTheBand = async (newBand, band) => {
let newLocation = []
let accessToken = ''
//I need to geo-locate each band for my new system. So I use the google api to do so.
try {
let resposne = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${band.bandLocation}&key=xxxxxxxxx`)
const responseLocation = await resposne.json()
newLocation = [responseLocation.results[0].geometry.location.lng, responseLocation.results[0].geometry.location.lat ]
// I store the geo location for the band and now I post the new band to my database. The Geo Location is Indexed and does not work if I post it - I need to do this after the band is posted - no clue why.
response = await fetch('http://localhost:5000/api/autoquotegenerators', {
method: 'POST',
headers: {
"Content-Type": 'application/json; charset=UTF-8'
},
body: JSON.stringify(newBand)
})
const responseData = await response.json()
if(responseData){
console.log('Band Added');
//Like I said before... I need to add the geo-location now. So this function will do so.
updateBandLocation({
geometry: {
coordinates: newLocation,
city: band.bandLocation,
}
}, responseData._id, responseData, band)
}
} catch (error) {
console.log(error)
}
}
//I send the geo-location data to this function... it also takes care of a few last minute things such as posts, and quote figures and such. It all works.
const updateBandLocation = async (newLocation, bandUserId, band, oldBand) => {
console.log('adding location...')
let newPosts = [...band.posts]
let newQuoteGenerator = []
if(oldBand.timedRate > 0 ){
newQuoteGenerator.push({
cardType: "timed",
inputType: "select",
cardTitle: "Hourly Rate",
charge: oldBand.timedRate,
price: 0,
cardId: uuidv4()
})
}
if(oldBand.youtube1){
newPosts.push({
type: "video",
data: oldBand.youtube1,
date: new Date(),
postId: uuidv4(),
rockOn: []
})
}
if(oldBand.youtube2){
newPosts.push({
type: "video",
data: oldBand.youtube2,
date: new Date(),
postId: uuidv4(),
rockOn: []
})
}
try {
const response = await fetch(`http://localhost:5000/api/autoquotegenerators/${bandUserId}`, {
method: 'PUT',
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
body: JSON.stringify({
quoteGenerator: newQuoteGenerator,
bandLocation: newLocation,
posts: newPosts
})
})
const responseData = await response.json()
bandCount += 1
console.log(bandCount)
} catch (error) {
console.log(error)
}
}
getAccessToken()

unable to get correct data using `useLazyLoadQuery` when compose fragment into query

I do see a sucessfull API request
const data = useLazyLoadQuery<brandQuery>(
graphql`query
brandQuery {
...brand_autoBrands
}
`,
{
first: 10,
},
{
fetchPolicy: "network-only",
}
);
console.log(data);
But I got following output. it is supposed to be a json object returned from my API.
brand_autoBrands fragment somewhere
const autoBrands = graphql`
fragment brand_autoBrands on AdminQuery
#argumentDefinitions(
first: { type: "Int", defaultValue: 10 }
after: { type: "String", defaultValue: "" }
last: { type: "Int" }
before: { type: "String" }
filters: { type: "[Filter]" }
sorters: { type: "[Sorter]"}
)
#refetchable(queryName: "BrandListPaginationQuery") {
autoBrands(first: $first, after: $after, last: $last, before: $before, filters: $filters, sorters: $sorters)
#connection(key: "BrandList_autoBrands") {
edges {
node {
...brandFragment
}
}
pageInfo {
startCursor
}
totalCount
}
}
`;
const {
data,
loadNext,
hasNext
} = usePaginationFragment<BrandListPaginationQuery, _>(
autoBrands,
props.autoBrands,
);
You can't log a fragment data object in the parent. That data belongs to the component that defined a fragment. For performance, relay needs to know that this data is fetched for a child component but the parent doesn't need to know what the actual data object is. You just need to pass the data to its component then you should be able to log it in the child component.
const data = useLazyLoadQuery<brandQuery>(
graphql`query
brandQuery {
...brand_autoBrands
}
`,
{
first: 10,
},
{
fetchPolicy: "network-only",
}
);
data && <Brand autoBrands={data} />

Cloud Function for Firebase sendToDevice not showing as notification on ios

Upon sending a successful notification via Cloud Functions for Firebase, the notification is not shown as a push notification on ios device. I have found a couple of similar issues, but none present a clear solution.
cloud function:
exports.sendInitialNotification = functions.database.ref('branches/{branchId}/notifications/{notifId}').onWrite(event => {
const data = event.data.val()
if (data.finished) return
const tokens = []
const notifId = event.params.notifId
const getPayload = admin.database().ref(`notifications/${notifId}`).once('value').then(snapshot => {
const notif = snapshot.val()
const payload = {
notification: {
title: notif.title,
body: notif.message,
},
data: {
'title': notif.title,
'message': notif.message,
'id': String(notif.id),
}
}
if (notif.actions) {
payload.data['actions'] = JSON.stringify(notif.actions)
}
console.log('payload:', payload)
return payload
}, (error) => {
console.log('error at sendInitialNotification getPayload():', error)
})
const getTokens = admin.database().ref(`notifications/${notifId}/users`).once('value').then(snapshot => {
const users = snapshot.forEach((data) => {
let promise = admin.database().ref(`users/${data.key}/profile/deviceToken`).once('value').then(snap => {
if (tokens.indexOf(snap.val()) !== -1 || !snap.val()) return
return snap.val()
}, (error) => {
console.log('error retrieving tokens:', error)
})
tokens.push(promise)
})
return Promise.all(tokens)
}, (error) => {
console.log('error at sendInitialNotification getTokens()', error)
}).then((values) => {
console.log('tokens:', values)
return values
})
return Promise.all([getTokens, getPayload]).then(results => {
const tokens = results[0]
const payload = results[1]
if (payload.actions) {
payload.actions = JSON.stringify(payload.actions)
}
const options = {
priority: "high",
}
admin.messaging().sendToDevice(tokens, payload, options)
.then(response => {
data.finished = true
admin.database().ref(`notifications/${notifId}`).update({success: true, successCount: response.successCount})
console.log('successfully sent message', response)
}).catch(error => {
data.finished = true
admin.database().ref(`notifications/${notifId}`).update({success: false, error: error})
console.log('error sending message:', error)
})
})
})
...and the logs in firebase console:
successfully sent message { results: [ { error: [Object] } ],
canonicalRegistrationTokenCount: 0, failureCount: 1, successCount:
0, multicastId: 7961281827678412000 }
tokens: [
'eS_Gv0FrMC4:APA91bEBk7P1lz...' ]
payload: { notification: { title: 'test07', body: 'test07' }, data:
{ title: 'test07', message: 'test07', id: '1502383526361' } }
...but alas no notification shown on iphone. Im sure I am missing something along the OOO (order of operations) here, but not sure where the snag is. If anyone can point out my flaw please feel free to publicly chastise.
As always thank you in advance and any and all direction is appreciated!
The issue I was seeing was due to the reverse domain id in xcode not matching what was in firebase.
Typo

Breeze issue with complexType change tracking

I use Angular and Breeze in my app and I use "hasChangesChanged" event for handling a state of entities stored in EntityManager. I have a problem when I have entity with property that is complexType and isScalar=false.
The problem occurs when I make request twice (without changing any entity) and get the same entity. On second request "hasChangesChanged" event is fired with hasChanges=true.
In moment when this event is fired my entity has state "Modified", but after data are loaded that state is changed to "Unchanged".
I've wrote a (Jasmine) unit test. In comments are information which assertion throws error.
var entity,
hasChanges = false,
listeners = {
onChange: function (event) {
console.log('change', event.hasChanges);
}
};
spyOn(listeners, 'onChange');
$httpBackend.expectGET('json/SampleEntity?').respond(200, [
{
id: 1,
name: 'some name',
data: {},
$type: 'SampleEntity',
elements: [
{
etype: 'el1'
}
]
}
]);
manager.hasChangesChanged.subscribe(function (event) {
hasChanges = event.hasChanges;
});
var query = new breeze.EntityQuery('SampleEntity');
manager.executeQuery(query).then(function (data) {
entity = data.results[0];
});
$httpBackend.flush();
expect(hasChanges).toBe(false); // OK
expect(entity.entityAspect.entityState.isUnchanged()).toBe(true); // OK
$httpBackend.expectGET('json/SampleEntity?').respond(200, [
{
id: 1,
name: 'some name',
data: {},
$type: 'SampleEntity',
elements: [
{
etype: 'el1'
}
]
}
]);
manager.executeQuery(query).then(function (data) {
entity = data.results[0];
});
$httpBackend.flush();
expect(hasChanges).toBe(false); // ERROR
expect(entity.entityAspect.entityState.isUnchanged()).toBe(true); // OK
Is this expected behavior? And if not how I can fix it?

How to initialize the selection for rails-select2 in BackboneForms schema?

The project uses marionette-rails, backbone-on-rails, select2-rails and this port to BackboneForms to provide a multiselect form field. The select options are available to the user. They are retrieved from the collection containing the total list of options:
MyApp.module("Products", function(Products, App, Backbone, Marionette, $, _) {
Products.CustomFormView = Products.CustomView.extend({
initialize: function(options) {
this.model.set("type", "Product");
Products.EntryView.prototype.initialize.apply(this, arguments);
},
schemata: function() {
var products = this.collection.byType("Product");
var productTypes = products.map(function(product){
return {
val: product.id,
label: product.get("name")
};
});
return {
productBasics: {
name: {
type: "Text",
title: "Name",
editorAttrs: {
maxLength: 60,
}
},
type: {
type: 'Select2',
title: "Product type",
options: {
values: productTypes,
value: [3, 5],
initSelection: function (element, callback) {
var data = [];
$(element.val().split(",")).each(function () {
data.push({id: this, text: this});
});
callback(data);
}
},
editorAttrs: {
'multiple': 'multiple'
}
}
}
};
}
});
});
Do I initialize the value correctly in options.value? How comes initSelection is never called? I copied the function from the documentation - it might be incomplete for my case. None of the products with the IDs 3 and 5 is displayed as the selection.
initSelection is only used when data is loaded asynchronously. My understanding is that there is no way of specifying the selection upon initialization if you are using an array as the data source for a Select2 control.
The best way of initializing the selection is by using setValue after the form is created. Here is a simplified example based on the code in your example.
var ProductForm = Backbone.Form.extend({
schema: {
type: {
type: 'Select2',
title: "Product type",
options: {
values: productTypes,
},
editorAttrs: {
'multiple': 'multiple'
}
}
});
var form = new ProductForm({
model: new Product()
}).render();
form.setValue("type", [3, 5]);
You can use value function (http://ivaynberg.github.io/select2/#documentation) in setValue. I personally recomend you to use this backbonme-forms plugin: https://gist.github.com/powmedia/5161061
There is a thread about custom editors: https://github.com/powmedia/backbone-forms/issues/144

Resources