Openpgp Encrypt with Multiple Keys - encryption

I'm trying to encrypt string with multiple keys but I get that error;
TypeError: Error encrypting message: key.getPrimaryUser is not a function
let pubKey = ['-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: Keybase OpenPGP v1.0.0\r\nComment: https:\/\/keybase.io\/crypto\r\n\r\nxo0EXWqkRQEEAKiSir3G77kAQAjpsNLjIoqBI7Z\/jR5TOaUxO7Ul0NiQn84BIEIG\r\nD44ZkEMjStg+ExH2EQFkjDzdxT633H\/c1NNCfivmFJvLEHYg\/YliQ9dLg8PAW9jl\r\nxn67xGkspogfURdq8i0GpPh28K8RojpRv3U6m5mS5z9SF8yCQwagcIt9ABEBAAHN\r\nFmFzZGFzZCA8YXNkYXNAYXNkLmNvbT7CrQQTAQoAFwUCXWqkRQIbLwMLCQcDFQoI\r\nAh4BAheAAAoJEK+6HfpyjgvY7H4D\/1HotnF3pVVNqG4cLJsH0ix\/za2+07495rkH\r\n+GoWQC2YEdLzV1L41pR\/JmqmQrXZp0M6txpP5\/q6cBQGgcv+u02TLzSzrO0gfzFf\r\nXktVcdr7+8GS2I\/0cFhpqEcxo8ph+uMSZUllzAM+TmeXLbwg8u8otb\/m9cLCc6vn\r\na9mxf86vzo0EXWqkRQEEAObmlGPvG7uCZuRtoccOtiNItzAxMFYGfQvvShvV17Jo\r\nyGP18mt2HF\/9vmb2QDbkNPxIM1JIT54u0Oh0W4Kc3sfCia1kn6CiG+fdbybtvr4z\r\npqyOEsprPe4klFyMIiI10oxE+qTbJn4JViHx12gZCYpjyJr76qIsMWD1r7uRk9Xh\r\nABEBAAHCwIMEGAEKAA8FAl1qpEUFCQ8JnAACGy4AqAkQr7od+nKOC9idIAQZAQoA\r\nBgUCXWqkRQAKCRCz1jcggxBJRrFlA\/9qt11ba9M6R8wV21AoAcWRydSeY7xgMqHO\r\nIb4aEZCwj+sZs4igE\/FXWfvOl0c5tCAeC8YQkqvL6vHx7128oM2OkIc0pTEVRHak\r\nbuYDBKEVnorY+hcjsoijx0HxwKkgJ6Vj7ciVQc9ofK2kmIXtQF0p28W9anvGN+uE\r\nwj4LKF56uAj\/A\/9NhIJtnVH0uPixu7eQNr1R1CWfmYxnOnaShpSRcTdzEVMKMJ4O\r\n3Tyou7lKRKryF8YgWTzYsl7WUCLbNtiKeV7VM4av6YVEjgO4wK2U+jbT1ITFuTbb\r\nuYxCozveZcaNcNzpSOtgG2BzRn1Z2+\/9Fvbm2ucAw4kR4wQMZ5FFbU1Ikc6NBF1q\r\npEUBBACpqRZ0AU0trrcD4tF2gIRuL41k1bu2nMkqira\/yJqCEz5EXKe+1R1zYXdx\r\nYSFT7WV0PzJCW02vHpygdIqW1YTkeIvU3u\/HN7LEBzlZDJiGrdPpMIL7UXswKg5v\r\nPLv8YSAcLN1g2k8z0uFx2NUBkt+Crjwr60MFxmGAkfbeg0arOQARAQABwsCDBBgB\r\nCgAPBQJdaqRFBQkPCZwAAhsuAKgJEK+6HfpyjgvYnSAEGQEKAAYFAl1qpEUACgkQ\r\nAQ9qPQxcPBRF9wP8Cl8VJp0+EVziJtxRZUwThlfp3Q4zkCxtNk+zwQWTKpb\/MRhN\r\n53t+1bshYxNeh1TauAmtj3qLkciq6WyRTWkNTcV6aSfCrKQ6oMcADBFOu\/dVfkaD\r\nibF\/u24nP0IzajMlasgFFQQzwiPfP9mYP9lWyz3Los4sE5yqknmxpWSAXNkhuwP\/\r\nRTLHCO1qKB1pNhoT5XTBzrEvle8lOo+l2P\/dyT+jaFzg1xBoHe3TA6fRnKrQl1N+\r\nHUBnFLjT+9\/DsxkqHIRXe\/rhYxm\/xziLsnazlvWG6uOKFVXAhsxwhe+X2CuFMxO0\r\ntnaYILB4TGqQCQ+VQMK8eKtkFZPDcS3nAgiKxj8tyk0=\r\n=Eocd\r\n-----END PGP PUBLIC KEY BLOCK-----', '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: Keybase OpenPGP v1.0.0\r\nComment: https:\/\/keybase.io\/crypto\r\n\r\nxo0EXWqkqwEEANx2Jvt5bVIF\/ABbVV9LTsX5HzZkmbWuTcSl4vOZ\/lK9rF1gRtol\r\nj7Me3rqdb\/dM0dP9jU1TpYG\/n726UxZI\/uyXZXjYcinXs0XLBhl6itVqiELMGNpY\r\nE7mud27NnK3vzdOmVsq0RhYh73RYA7KH1mBsICD2rwclYCfDr2rqedzDABEBAAHN\r\nF2FzZGFzZCA8c2FkYXNkQGFzZC5jb20+wq0EEwEKABcFAl1qpKsCGy8DCwkHAxUK\r\nCAIeAQIXgAAKCRB+jw5+xGpD2KnUA\/9jNtIgdWGrR3zB+qY0r+oTRugpA5hPZuPm\r\nMSlZi3jtomBAZzaMfAYcFbf6GJ81tyQ6AjIMwfMidymNPsACo0UxfmeEpJtZlepn\r\ndR2ht7oSnyEIw72+O9SqpweqWhvQLWlvhxr9BKlOGYav5gcu36yclPcK8vM2bJz2\r\nfWIuSTe4Ac6NBF1qpKsBBACt5Si6mBzm3mqYXCK5PLrjzLHIEQMOWo1uA17dpCc1\r\niCWeHDNqZwlnZfbwOHvujVrvWQ2bcRCXx+m9USolGArDjHmHLWpQ9yhAiz2r4JLt\r\nfDShyL+VIa6C+JsEFS0iIfp7b\/nXxecgkiTAbuvJajGvpEqlGdziicbm\/6NJ8E8f\r\nKQARAQABwsCDBBgBCgAPBQJdaqSrBQkPCZwAAhsuAKgJEH6PDn7EakPYnSAEGQEK\r\nAAYFAl1qpKsACgkQ+Vzpbkqbd5\/HqwQAmIp65oKl36jFrDe3Rjn\/L802RAjOW1Zz\r\nNDe9mamLfKDCG7nFXp6AmMKIZAT4\/mbOfAGKP\/pOOJhTdqDdipEFQRNHNjT3rlPl\r\ndM7DCFQxYF3yZ80hAZjfVkHLb9bnji+\/EUXCFDEwihGtPWKFlPtg9vTCGcrMJ18N\r\nIaNbKDtnAT\/jagP9GiYOhv3OznOlN5Zj0GR9QEVmoycmciOIARXm8dRQoA8yIJVK\r\ny+kFOxQPqTkVvk9859DgrHhJJ4G0J0j3ps6CremvW1eeWzbfnGPwLjOKfOpWjv6b\r\nQrMubb7XRZUhxWyJ2fGd\/FNHsOiIOfSOfIaN+0J0FxMPMK5hvTHMkXlZQYbOjQRd\r\naqSrAQQAwvh2OjBbeLMFU2vbH0HsrdbftNnKhI6ZT\/X7MwQZwpNfGLLcMBRx79DZ\r\nFFdwpgwezOUf9zH+mwzGkLfzcU3MWi3Akro\/ZPMun8is8OaVpcd3kWWA6njX0tpj\r\nE43lEzACvNV+uI5hOKm3htKvMZQusjRI67xwBJjWoL2jlR6HyDUAEQEAAcLAgwQY\r\nAQoADwUCXWqkqwUJDwmcAAIbLgCoCRB+jw5+xGpD2J0gBBkBCgAGBQJdaqSrAAoJ\r\nEEvNtk\/PsdsaANAD\/3QVxC6LzphgFv1O4S4gKH1MNZGkfzWmw7XGBriodxwEq3Fh\r\nxkAKh5TsY9UQsTVrvY+PeDaWIEO26ZOCgsc8mW\/oFEppWfB9RpZOw02fTcYU+PZ8\r\nWs61FUGLdDx8+LwIZqXvBMeLdZRXB0GxhDrTuH\/L\/tjZLQ3Et2rvye4Tw4mbnDgD\r\n\/iyrhKzD1hsTPbX9qE0wbFpHQDZr+ud5i+4xpVEtXfanjDFOk2FKNmmT+yGEm7rU\r\nQesXTgkYYpONdvbHwl800mftzPxQlmmfqjZ7P8IYjJvZG0LAbGjsH\/JdevTDDGBK\r\nIEvOTSu34SI68FufqQTDKiWQWo1mBej98qsSoE7SAhcx\r\n=owPm\r\n-----END PGP PUBLIC KEY BLOCK-----'];
let message = "Hello World";
function encryptData(pubkeys, plainText){
let result;
let sync = true;
const encryptDecryptFunction = async() => {
console.log('working');
pubkeys = pubkeys.map(async (key) => {
return (await openpgp.key.readArmored(key)).keys[0]
});
const options = {
message: openpgp.message.fromText(plainText), // input as Message object
publicKeys: pubkeys, // for encryption // for signing (optional)
}
openpgp.encrypt(options).then(ciphertext => {
result = ciphertext.data // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
sync = false;
}).catch((err) => {
console.log(err);
})
}
encryptDecryptFunction();
while(sync){require('deasync').sleep(100);}
return result;
}
encryptData(pubKey, message)

I faced this same issue and it happens because in the part where you load the variable pubkeys :
pubkeys = pubkeys.map(async (key) => {
return (await openpgp.key.readArmored(key)).keys[0]
})
needs to be changed to promises like this:
let publicKeysPromises = pubkeys.map(async (key) => {
return (await openpgp.key.readArmored(key)).keys[0]
});
Since it is an async operation and using the 'pubkeys' in your case in 'options' variable before it is fully resolved causes the issue. This happens because openPGP makes internal call to a function called getPrimaryUser when the function 'encrypt' is called with the 'options' {openpgp.encrypt(options)}. And when the public keys are read with readArmored it creates an Key object which in turn has 'getPrimaryUser' attached to it as an async function. Therefore the above promises need to be resolved before you can call the 'encrypt' function.
Therefore, next, you need to change the options to the code below :
const options = {
message: openpgp.message.fromText(message),
// resolve all promises returned before
publicKeys: await Promise.all(publicKeysPromises), // for encryption
privateKeys: [privKeyObj] // for signing (optional)
}
I hope this answers the question.

Related

Status 400 on post request when adding a paramater on Angular (and C# signalR)

I'm work on chat app between specific clients using Angular and SignalR. So far everything is working good except one thing - when I'm sending a message to specific user, I want to send it also to myself.
After a lot reading of reading, I've realized that I can't get the Context value unless I'm approaching directly to my hub. I found a way to go around it by invoking GetSenderName() method (to get the sender's name) and then send it back to the server's controller. The problem is when I've added the parameter fromUser to my request . Before I've added it the request, I've reached the server and after I've added it I'm getting-
Status code 400
I've tried to debugg the client and it seems like all the parametrs has a valid values so I don't understand what went worng. Can anyone help me please?
User component (only the relevant part)-
fromUser: string="";
sendMessageToUser(sendToUser: any): void {
this.sendToUser=sendToUser;
this.fromUser=this.signalRService.getName();
console.log("fromUser: ", this.fromUser);
console.log("check sendToUser: ", this.sendToUser);
this.signalRService.sendMessageToUser(
this.sendMessageToUserUrl,
this.chatMessage,
this.sendToUser,
this.fromUser
);
}
SignalR Service-
public fromUser: string="";
constructor(private http: HttpClient) { }
public startSignalrConnection(connectionUrl: any) {
return new Promise<any>((resolve, reject) => {
this.hubConnection = new HubConnectionBuilder()
.withUrl(connectionUrl, {
withCredentials: false,
accessTokenFactory: () => localStorage.getItem('jwt')!,
})
.configureLogging(LogLevel.Debug)
.build();
this.hubConnection.start()
.then(() => {
console.log('in');
resolve(this.hubConnection.connectionId);
})
.then(()=>this.getSenderName())
.catch((error) => {
reject(error);
});
public getSenderName=()=>{
this.hubConnection.invoke('getSenderName')
.then((data)=>{
console.log("this is the data: ", data);
this.fromUser=data;
})
}
public getName(): string{
return this.fromUser;
}
This is where the problem is (SignalR Service)-
public sendMessageToUser(sendMessageToUserUrl: string, message: string, sendToConnId: any, fromUser:string){
firstValueFrom(this.http.post(sendMessageToUserUrl, buildChatMessageModel(sendToConnId, message, fromUser)))
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log("Failed to send message: ", error);
alert("Failed to send message: "+ error);
})
}
ChatMessageModel-
export interface ChatMessageModel {
ConnectionId: string,
Message: string
FromUser: string
}
Utils-
export const buildChatMessageModel = (hubConnectionId: string, message: string, fromUser: string): ChatMessageModel => {
return {
ConnectionId: hubConnectionId,
Message: message,
FromUser: fromUser
};
};

Trouble reading data in Firebase Cloud Function

Trying to read a pushToken from a given user in the users collection (after an update operation on another collection) returns undefined
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
})
.then(r => {
getToken(idTo).then(token => {
// sendMsg...
})
}).catch(updateErr => {
console.log("updateErr: " + updateErr)
})
async function getToken(id) {
let response = "getTokenResponse"
console.log("id in getToken: " + id)
return db.collection('users').doc(id).get()
.then(user => {
console.log("user in getToken: " + user.data())
response = user.data().pushToken
})
.catch(e => {
console.log("error get userToken: " + e)
response = e
});
return response
}
return null
});
And this is from the FB console log:
-1:43:33.906 AM Function execution started
-1:43:36.799 AM Function execution took 2894 ms, finished with status: 'ok'
-1:43:43.797 AM id in getToken: Fm1RwJaVfmZoSgNEFHq4sbBgoEh1
-1:43:49.196 AM user in getToken: undefined
-1:43:49.196 AM error get userToken: TypeError: Cannot read property 'pushToken' of undefined
-1:43:49.196 AM returned token: undefined
And we can see in this screenshot from the db that the doc does exist:
Hope someone can point me to what I'm doing wrong here.
added screenshot of second example of #Renaud as deployed:
As Doug wrote in his comment, you need to "return a promise from the top level function that resolves when all the async work is complete". He also explains that very well in the official video series: https://firebase.google.com/docs/functions/video-series/ (in particular the 3 videos titled "Learn JavaScript Promises"). You should definitely watch them, highly recommended!
So, the following modifications to your code should work (untested):
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite(async (snap, context) => { // <- note the async keyword
try {
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
await db.collection('Classificados').doc(classificadoId)
.update({
aprovado: false
});
const userToSnapshot = await db.collection('users').doc(idTo).get();
const token = userToSnapshot.data().pushToken;
await sendMsg(token); // <- Here you should take extra care to correctly deal with the asynchronous character of the sendMsg operation
return null; // <-- This return is key, in order to indicate to the Cloud Function platform that all the asynchronous work is done
} catch (error) {
console.log(error);
return null;
}
});
Since you use an async function in your code, I've used the async/await syntax but we could very well write it by chaining the promises with the then() method, as shown below.
Also, I am not sure, in your case, that it adds any value to put the code that gets the token in a function (unless you want to call it from other Cloud Functions but then you should move it out of the addDenuncia Cloud Function). That's why it has been replaced by two lines of code within the main try block.
Version with chaining promises via the then() method
In this version we chain the different promises returned by the asynchronous methods with the then() method. Compared to the async/await version above, it shows very clearly what means "to return a promise from the top level function that resolves when all the asynchronous work is complete".
exports.addDenuncia = functions.firestore
.document('Denuncias/{denunciaID}')
.onWrite((snap, context) => { // <- no more async keyword
const doc = snap.after.data()
const classificadoId = doc.cid
const idTo = doc.peerId
return db.collection('Classificados').doc(classificadoId) // <- we return a promise from the top level function
.update({
aprovado: false
})
.then(() => {
return db.collection('users').doc(idTo).get();
})
.then(userToSnapshot => {
if {!userToSnapshot.exists) {
throw new Error('No document for the idTo user');
}
const token = userToSnapshot.data().pushToken;
return sendMsg(token); // Again, here we make the assumption that sendMsg is an asynchronous function
})
.catch(error => {
console.log(error);
return null;
})
});

HTTPS Callable Function that returns async function returning empty object

I have a HTTPS Callable function of with the following structure:
/**
* Accepts a friend request from another user
*/
export const acceptFriendRequest = functions.https.onCall(
(data : standardStructs.fromToStruct, context) => {
standardChecks(data, context)
if (!context.auth || context.auth.uid === data.to){
throw new functions.https.HttpsError(
'invalid-argument',
'You cannot do this operation to yourself!');
}
return async () => {
const fromSnapshot = await admin.database().ref(...).once('value');
const toSnapshot = await admin.database().ref(...).once('value');
const inboxSnapshot = await admin.database().ref(...).once('value');
const updates = {} as any;
const response = {} as any
updates[`...`] = null;
updates[`...`] = null;
//If the desintation doesn't exist, then let's just erase this friend request
if (!toSnapshot.exists()){
response.status = standardHttpsData.returnStatuses.NOTO
}else if (!inboxSnapshot.exists()){
//This user is trying to accept a request that was never sent to them
response.status = standardHttpsData.returnStatuses.INVALID
}else{
updates[`...`] = toSnapshot.val();
updates[`...`] = fromSnapshot.val();
response.status = standardHttpsData.returnStatuses.OK
}
await admin.database().ref().update(updates);
return response
};
});
For some reason, this function returns an empty object. Additionally, it doesn't make any writes to the database, even though the necessary snapshots exists for it to do so.
I'm not sure why, because I am indeed retuning a promise, just like the documentation says I should if I'm doing asynchronous commands.
Instead of returning an async function within your Callable Cloud Function, you should declare the handler function as async, as follows:
export const acceptFriendRequest = functions.https.onCall(
async (data : standardStructs.fromToStruct, context) => {
standardChecks(data, context)
if (!context.auth || context.auth.uid === data.to){
throw new functions.https.HttpsError(
'invalid-argument',
'You cannot do this operation to yourself!');
}
const fromSnapshot = await admin.database().ref(...).once('value');
const toSnapshot = await admin.database().ref(...).once('value');
const inboxSnapshot = await admin.database().ref(...).once('value');
//...
await admin.database().ref().update(updates);
return response
});
You don't give details on what is the standardHttpsData object and associated returnStatuses (nor you give details on standardChecks), but you should note that you need to return, in your Cloud Function, some "data that can be JSON encoded", see the doc.
Update following your comment above:
The solution proposed here will work with your returnStatuses map, since the map value can be JSON encoded. However, note that the documentation recommends handling the error in a different way, using an instance of functions.https.HttpsError.

Firebase Cloud Function (async) not returning result

I am rather new to Firebase/Firestore/Cloud functions and been trying a little project where a client app calls a Firebase Cloud Function to generate some random keys (random numbers), adds them to Firestore, and when successfully written, returns these keys to the client app. Kinda like a random number generator.
The function is called correctly by the client (according to Firebase Console), does generate the keys, checks if they exist in the Firestore, and if not adds them. All works up to the part where it should return the result to the client. Here the client never gets the result (the keys array). In fact, the callback in the client app (iOS/Swift) is never called.
I am suspecting the problem lies in the promise being returned? According to the Firebase documentation here, async callables should return a Promise although I am not entirely sure what I am doing is correct https://firebase.google.com/docs/functions/callable
Here is the code for the cloud function:
export const generateRandomKeys = functions.https.onCall(async (data, context) => {
// Read data passed from client
const numberOfKeys = data.numberOfKeys
console.log("Number of keys to generate: ", numberOfKeys)
// Generate some keys
const generatedKeys = KeyMaker.newKeys(numberOfKeys)
try {
const randomkeys = []
// Write keys to DB
for (const key of generatedKeys) {
const addedKey = await writeKeyToDB(key)
randomkeys.push(addedKey)
}
return Promise.resolve(JSON.stringify(randomkeys))
} catch (error) {
console.log("Error has occured: ", error)
throw new Error("An Error has occured: " + error)
}
})
async function writeKeyToDB(key: string){
try {
// Check if a document with the same key already exists in the DB
const docRef = db.collection("randomKeys").doc(key)
const doc = await docRef.get()
// Document with same key found!
if (doc.exists) {
// Generate a new key and try again
const newKey = KeyMaker.newKey()
console.log("Will generate a new key and try again!. New key: ", newKey)
await writeKeyToDB(newKey)
}
const keyDoc = {
somefield: somevalue,
}
// Write to DB then return result
await docRef.set(keyDoc)
return Promise.resolve(key)
} catch (error) {
return Promise.reject(error)
}
}
Client (Swift)
public static func generateNewRandomNumbers(numberOfKeys: Int) {
FirebaseApp.configure()
let functions = Functions.functions(region: FIRESTORE_REGION)
functions.httpsCallable("generateRandomKeys").call(["numberOfKeys": numberOfKeys]) { (result, error) in
// Nothing here executes
print("----------got reply---------")
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
print("Error \(String(describing: code)): " + message)
}
}
if let keys = (result?.data as? [String]) {
dump(keys)
}
}
}
Dont combine Async/Await and Promise. Async functions as itself returning Promise.
First change return of your cloud function to :
return JSON.stringify(randomkeys);
Also in writeKeyToDb change return to:
return key;
and catch part to:
throw Error(error);
I see also problem that in cloud function you calling your writeKeyToDb function with 2 parameters, but that function have only one. But that code probably is in progress
Finally found the issue, thanks for Doug and Dominik for guiding me in the right direction. I removed the promises and returned directly the values but more importantly, I didn't need to convert the array to JSON. I came across HTTPSCallableResult documentation
I simply changed this
return JSON.stringify(randomkeys);
to
return randomkeys
and on the client, instead of
if let keys = (result?.data as? [String]) {
dump(keys)
}
I do
if let keys = (result?.data as? NSArray) {
dump(keys)
}

Firebase Cloud Function Does Not Send Response

here is my code of http cloud function that reads some documents and then send response
res.set('Access-Control-Allow-Origin', '*');
var orderId;
var result = "";
var userId;
var promoCode;
var promoRef;
var userDocRef;
var promoCodeDoc;
//userId = req.body.userId;
//orderId = req.body.orderId;
promoCode = req.body.promoCode;
//userDocRef = db.collection("Users").doc()
promoRef = db.collection("PromoCodes").doc(promoCode);
var transaction = db.runTransaction(t => {
return t.get(promoRef)
.then(promoCodeDoc => {
if(promoCodeDoc.exists){
result = "OK";
res.json(result);
}else{
result = "Invalid Promocode!";
res.json(result);
}
//t.update(cityRef, {population: newPopulation});
return true;
});
}).then(result => {
console.log('Transaction success!');
return true;
}).catch(err => {
console.log('Transaction failure:', err);
});
return Promise.all(transaction());
But This is not sending the response because functions ends but Firestore Transaction is still runnning in background .
Any Solution to my problem ?
Promise.all() expects a single array of promises as its argument, but you're not giving it an array argument. Secondly, the transaction variable is a promise, not a function. You can't call () a promise.
So I think the correct code would be return Promise.all([transaction]). This being said, you only have one promise so you don't need Promise.all and can just return transaction.
Not sure if this will solve all your problems though. If you log into the firebase console, navigate to the functions section, there's a "Logs" tab that allows you to see debugging output from your function executions. It might help you track down all the problems. I imagine there are already console errors logged pointing out the fact that transaction() is not a function.

Resources