How to asynchronuosly contruct a DataSource? - javascript

I'm trying to create a DataSources class that interacts with Google Sheets via Sheets API
To start reading sheets, the API needs to do an asynchronous authorization process. How should I write my class since it relies exclusively on this async event?
The auth process goes something like this:
authorize(credentials).then(auth => {
// I have to init the api here because it needs an auth object returned asynchronously
const api = google.sheets({ version: "v4", auth });
});
How should I initialize the class so I can use api inside every method?
import { google } from "googleapis";
import * as fs from "fs";
import { authorize, CRED_PATH } from "../../sheetsAuth";
import { DataSource } from "apollo-datasource";
class SheetsAPI extends DataSource {
sheetID: any;
api: any;
constructor(sheetID) {
super();
this.sheetID = sheetID;
// ideally I initialize the api here, but is asynchronous
authorize(credentials).then(auth => {
this.api = google.sheets({ version: "v4", auth });
});
}
async getRows() {
// ideally I want to have access to the authorized api here but since its async it returns undefined
const r = await this.api.spreadsheets.values.get({
spreadsheetId: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
range: "Class Data!A2:E",
});
if (r) {
const rows = r.data.values;
if (rows) {
if (rows.length) {
return rows;
}
}
}
}
}
export { SheetsAPI };
How can I overcome this?

Right now I decided to go with this approach:
Create a special method to initialize the authorization before every method call.
class SheetsAPI extends DataSource {
sheetID: any;
api: any;
constructor(sheetID) {
super();
this.sheetID = sheetID;
}
async init() {
//init returns a promise I can await so I'm sure this.api will be defined before I use it
const auth = authorize(JSON.parse(fs.readFileSync(CRED_PATH, "utf8"))).then(
(auth) => {
this.api = google.sheets({ version: "v4", auth });
}
);
}
async getRows() {
// I have to await this.init every time I need to use this.api
await this.init();
const r = await this.api.spreadsheets.values.get({
spreadsheetId: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
range: "Class Data!A2:E",
});
if (r) {
const rows = r.data.values;
if (rows) {
if (rows.length) {
return rows;
}
}
}
}
}

Related

i want to code this react function to get data from thirdweb and display

Help me to get fetch information from blockchain and display in the browser. i want know how to call this thirdweb functions in react.
Below code is a solidity code used to create a user in our system.
function createUser(string memory _userId, string memory _fName, string memory _lName, string memory _mobile, string memory _dob, uint256 _age, string memory _nationality, string memory _gender) public {
if(!chkexisitinguserId(_userId)){
users[_userId] = User(_fName, _lName, _mobile, _dob, _age,_nationality,_gender);
noofUser++;
allUserId[k] = _userId;
k++;
}
}
function getUser(string memory _userId) public view returns (string memory, string memory, string memory, string memory, uint256, string memory, string memory) {
User memory user = users[_userId];
return (user.fName, user.lName, user.mobile, user.dob, user.age, user.nationality, user.gender);
}
The below code is thirdweb libary code to interact with smart contract. The below code is stored in refer.js file.
import { useContract, useContractWrite } from "#thirdweb-dev/react";
export default function Component() {
const { contract } = useContract("0xBB417720eBc8b76AdeAe2FF4670bbc650C3E791f");
const { mutateAsync: createUser, isLoading } = useContractWrite(contract, "createUser")
const call = async () => {
try {
const data = await createUser([ "John0312", "John", "s", "8090890367", "03-11-2000", 20, "India", "M" ]);
console.info("contract call successs", data);
} catch (err) {
console.error("contract call failure", err);
}
}
}
export default function Component() {
const { contract } = useContract("0xBB417720eBc8b76AdeAe2FF4670bbc650C3E791f");
const { data, isLoading } = useContractRead(contract, "getUser", _userId)
}
The smart contract is deployed in thirdweb and trying to access it. I am stuck at how to call this "call" async function from app.js.
import React, { useEffect } from 'react'
function App(){
const handleclick = async (e) => {
await call();
}
return (
<button onClick={handleclick}>click me</button>
)
}
export default App
it generates error like undefined function call().
I would create a new hook (useCall.js) who's job is simply to instantiate the useContract and useContractWrite hooks, then define the call() method for you to use in any component.
In this example, call() is the only thing returned from the hook. It's wrapped inside useCallback to ensure it's only defined when createUser is defined.
export default function useCall() {
const { contract } = useContract("0xBB417720eBc8b76AdeAe2FF4670bbc650C3E791f");
const { mutateAsync: createUser, isLoading } = useContractWrite(contract, "createUser")
const call = React.useCallback(async () => {
try {
const data = await createUser([ "John0312", "John", "s", "8090890367", "03-11-2000", 20, "India", "M" ]);
console.info("contract call successs", data);
} catch (err) {
console.error("contract call failure", err);
}
}, [createUser]);
return call;
}
Now inside of any component you can use the hook and get the call() function:
import useCall from './useCall';
export default function Component() {
const call = useCall();
useEffect(() => {
(async () => {
await call();
})();
}, []);
}

vue async function returns [object promise]

UPDATE
CodeSandbox
What i try to do:
i am working on a permissionerMixin which should handle a websites permission for authenticated users.
for example if a logged in user has no permission to see a specific part of the website i handle the components with a v-if="allowedToSee". So far so good. i store the whole user permission object in my vuex store.
the data came from a rest api and looks like this:
const rights = [
{
name: 'findMe1',
value: false,
},
{
name: 'findMe2',
value: false,
},
{
name: 'findMe3',
value: false,
},
{
name: 'findMe4',
value: false,
}
]
now back to my mixin and and how i load the data from the api:
import axios from 'axios';
export const permissionerMixin = {
methods: {
async getRightsFromActiveUser() {
axios.get(`/not/the/real/path/${this.$store.state.User.activeUser.id}`)
.then((response) => {
return this.$store.commit('addActiveUserRights', response.data);
})
.catch((error) => {
console.log(error.response);
});
},
async permissionFor(rightName) {
const rights = await this.getRightsFromActiveUser();
for (const right of rights) {
if (right.name == rightName) {
return right.value;
}
}
}
}
}
as u can see i have two functions which work together.
getRightsFromActiveUser() is simply a getter for the right object i mentioned at the beginning.
it takes the actual data and puts it in the vuex store with a mutation:
const state = {
activeUser: {
id: 0,
permissions: {}
}
};
const getters = {};
const actions = {};
const mutations = {
addActiveUserId (state, id) {
state.activeUser.id = id;
},
addActiveUserRights (state, rights) {
state.activeUser.permissions = rights;
}
};
export default {
state,
getters,
actions,
mutations,
};
right after this we have the actual init function permissionFor(rightName) which should do the magic and should give me a boolean return value to handle the permissionings.
the one big problem now is that i instead of getting a boolean return, i get a [object Promise], thats because i am stupid and i don't get that promise thing in my head.
at the end i simply want to add this function to a vue component with an
v-if="permissionFor('whatEver')" to solve the permission handling.
pulled the return into it's own statement following the commit. Not sure what your response object looks like from the back end but this look a cleaner to me personally and able to be read later. Check it out and see how things change, if at all.
import axios from 'axios';
export const permissionerMixin = {
methods: {
async getRightsFromActiveUser() {
try {
let returnData = await axios.get(`/not/the/real/path/${this.$store.state.User.activeUser.id}`)
this.$store.commit('addActiveUserRights', response.data);
return returnData.data
} catch (error) {
console.log(error.response);
}
},
async permissionFor(rightName) {
try {
const rights = await this.getRightsFromActiveUser();
for (const right of rights) {
if (right.name == rightName) {
return right.value;
}
}
} catch(error){
console.log(error.response);
}
}
}
}

kuzzle / react native - Cannot inherits class from BaseController

I'm trying to extends the KUZZLE JavaScript SDK in order to call some controllers on kuzzle servers, implemented via plugins.
I'm following that guide: add controller
Here is my controller which extends from the BaseController:
const { BaseController } = require('kuzzle-sdk');
export class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And here is where I specify the controller in order to use it further in the App, on the creation of the singleton.
const {UserController} = require('./UserController');
const { Kuzzle, WebSocket } = require('kuzzle-sdk');
class KuzzleService {
static instance = null;
static async createInstance() {
var object = new KuzzleService();
object.kuzzle = new Kuzzle(
new WebSocket('localhost'),{defaultIndex: 'index'}
);
object.kuzzle.useController(UserController, 'user');
await object.kuzzle.connect();
const credentials = { username: 'admin', password: 'pass' };
const jwt = await object.kuzzle.auth.login('local', credentials);
return object;
}
static async getInstance () {
if (!KuzzleService.instance) {
KuzzleService.instance = await KuzzleService.createInstance();
}
return KuzzleService.instance;
}
}
export default KuzzleService;
Somehow I'm getting the following error:
Controllers must inherit from the base controller
Is there something wrong with the imports ?
I've found out the solution to that issue. Firstly, I was not on the right version of the kuzzle SDK released recently (6.1.1) and secondly the controller class must be exported as default:
const { BaseController } = require('kuzzle-sdk');
export default class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And then the UserController needs to be importer that way:
import UserController from './UserController.js'
Then, as specified in the documentation, we need just inject the kuzzle object into the controller that way:
kuzzle.useController(UserController, 'user');

Node.js: Awaiting a Require

I'm new the Node.js and I've been working with a sample project by a third party provider and I'm trying to use Azure Key Vault to store configuration values.
I'm having trouble getting a process to wait before executing the rest. I'll try to detail as much as I know.
The sample project has a file named agent.js which is the start page/file. On line 16 (agent_config = require('./config/config.js')[process.env.LP_ACCOUNT][process.env.LP_USER]) it calls a config file with values. I'm trying to set these value using Key Vault. I've tried many combinations of calling functions, and even implementing async / await but the value for agent_config always contains a [Promise] object and not the data returned by Key Vault.
If I'm right, this is because the Key Vault itself uses async / await too and the config file returns before the Key Vault values are returned.
How can Key Vault be added/implemented in a situation like this?
Here's what I've tried:
First updated agent.js to
let agent_config = {};
try {
agent_config = require('./config/config.js')['123']['accountName'];
} catch (ex) {
log.warn(`[agent.js] Error loading config: ${ex}`)
}
console.log(agent_config);
Test 1
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
Results
{ accountsId: undefined }
Test 2
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
async function config() {
module.exports = {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
config();
Results
{}
Test 3
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
config();
Results
{ accountId: { <pending> } }
Other
I've tried many others ways like module.exports = async (value) =< {...} (found through other questions/solutions without success.
I'm starting to think I need to do some "waiting" on agent.js but I haven't found good info on this.
Any help would be great!
One issue is that your getValue function is not returning anything as your returns need to be explicit.
(and without the promise being returned, there's nothing to await on)
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
You could also get away with less explicit returns using arrow functions..
const getValue = async (secretName, secretVersion) =>
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then(credentials => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion)
.then(response => response.Value);
});
Introducing the Azure Key Vault read, which is async, means your whole config read is async. There' nothing you can do to get around that. This will mean that the code that uses the config will need to handle it appropriately. You start by exporting an async function that will return the config..
async function getConfig() {
return {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
module.exports = getConfig;
In your agent code you call that function. This will mean that your agent code will need to be wrapped in a function too, so maybe something like this..
const Bot = require('./bot/bot.js');
const getConfig = require('./config/config.js');
getConfig().then(agentConfig => {
const agent = new Bot(agentConfig);
agent.on(Bot.const.CONNECTED, data => {
log.info(`[agent.js] CONNECTED ${JSON.stringify(data)}`);
});
});
The package azure-keyvault has been deprecated in favor of the new packages to deal with Keyvault keys, secrets and certificates separately. For your scenario, you can use the new #azure/keyvault-secrets package to talk to Key Vault and the new #azure/identity package to create the credential.
const { SecretClient } = require("#azure/keyvault-secrets");
const { DefaultAzureCredential } = require("#azure/identity");
async function getValue(secretName, secretVersion) {
const credential = new DefaultAzureCredential();
const client = new SecretClient(KEY_VAULT_URI, credential);
const secret = await client.getSecret(secretName);
return secret.value;
}
The DefaultAzureCredential assumes that you have set the below env variables
AZURE_TENANT_ID: The tenant ID in Azure Active Directory
AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant
AZURE_CLIENT_SECRET: The client secret for the registered application
To try other credentials, see the readme for #azure/identity
If you are moving from the older azure-keyvault package, checkout the migration guide to understand the major changes

How to test a public async function inside Class using Jest in a ReactJS/Typescript app

Property 'getUsersTotalPayout` does not exist on type typeof PayoutApi
My Class:
import { bind } from 'decko';
import BaseApi from './Base';
import * as NS from './types';
class PayoutApi extends BaseApi {
#bind
public async getUsersTotalPayout(userId: string): Promise<number> {
const params: NS.IGetUsersTotalPayoutRequest = { userId };
const response = await this.actions.get<{ payout: number }>(
'/api/get-total-payout',
params,
);
return response.data.payout;
}
}
export default PayoutApi;
The test file:
import PayoutApi from './LiquidityPool';
const endpoint = '/api/get-total-payout';
const userId = 'foo';
jest.mock(endpoint, () => ({
getUsersTotalPayout: jest.fn(() => Promise.resolve({ data: { payout: 100.21 } }))
}));
describe('API: getUsersTotalPayout', () => {
it('should make a request when we get images', () => {
// const testApi = new PayoutApi();
expect(PayoutApi.getUsersTotalPayout(userId)).toHaveBeenCalledWith(endpoint, 'GET');
});
});
Getting that error on expect(PayoutApi.getUsersTotalPayout).toHaveBeenCalledWith(endpoint, 'GET');
You are currently trying to call method on the class. Since it is not static you should instantiate object of class first.
let api = new PayoutApi();
expect(api.getUsersTotalPayout(userId).....)
since jest.mock mocks module not endpoint or XHR request your test will try sending live request to /api/get-total-payout. For handling that it's needed to know what XHR wrapper do you use. Say for fetch() there is nice wrapper-mocker and libraries like axios also have their equivalence.
As for test itself. It does not work if you call a method and make expect on its result. It should be running method that must call server and then checking for mocked XHR if it has been called with valid parameters:
api.getUsersTotalPayout(userId);
expect(fetch_or_other_wrapper_for_mocking_xhr.get_last_request_method()).toEqual('get', endpoint, userId)

Categories

Resources