I don't know what I could be doing wrong to get this error:
I've tried different promises, await, async combinations and nothing.
I've tried Promise.resolve()) and also .then(function().
Nothing stopped that error, what can I change to fix it?
#Controller()
export class AppController {
constructor(private httpSoap: HttpClient,
#InjectModel('product') private readonly productModel: Model<any>,
private xmlUtils: XmlUtils) { }
#EventPattern("next")
async handleMessagePrinted(data: Record<any, any>) {
let result = data;
this.createproduct(result);
this.insertproduct(result);
}
insertproduct(data: any) {
stringify(data);
this.productModel.insertMany(data);
}
async createproduct(job: any): Promise<any> {
return new Promise((async (resolve, reject) => {
// if (this.soapService.productCreate) {
const payload = job;
const xmlPayload = this.xmlUtils.parseJSONtoXML(payload);
this.insertproduct(stringify(xmlPayload)); //gravar na mongo
console.log("xmlPayload "+xmlPayload);
const headerRequest = {
'Content-Type': ContentTypeEnum.XML,
SOAPAction: SoapActionEnum.CREATE_SERVICE_product
};
const soap: ResponseInterface = await this.request("localhost:8080", xmlPayload, headerRequest, SoapType.product);
if (soap.error) {
reject(soap.error);
}
if (soap.status) {
if (soap.status.firewall.code === '000-000' || soap.status.firewall.code === '000-001') {
resolve(`product ${soap.body.Number} created successfully`);
} else if (soap.status.firewall.code === '000-998' && soap.status.fireWall.code === '623') {
reject({ error: soap.status.fireWall.description });
} else if (soap.status.firewall.code === '000-500' && soap.status.fireWall.code === 'BWENGINE-100029') {
const payloadSearch: productSearchDocument = new productSearchDocument();
payloadSearch.IsOperational = undefined;
payloadSearch.IsHistory = undefined;
payloadSearch.Qualification = `id='${job.data.id_ID}'`;
const search = await this.searchproduct(payloadSearch);
if (search.status) {
if (search.status.firewall.code === '000-000' || search.status.firewall.code === '000-001') {
resolve(`product ${soap.body.Number} created successfully`);
}
} else {
reject({ error: search.status.firewall.description, fireWallError: soap.status.fireWall.description });
}
} else {
reject({ error: soap.status.firewall.description, fireWallError: soap.status.fireWall.description });
}
}
}));
}
public async searchproduct(data: any): Promise<any> {
return new Promise((async (resolve, reject) => {
// if (this.soapService.productSearch) {
const payload = data;
const xmlPayload = this.xmlUtils.parseJSONtoXML(payload);
const headerRequest = {
'Content-Type': ContentTypeEnum.XML,
SOAPAction: SoapActionEnum.SEARCH_SERVICE_product
};
const soap: ResponseInterface = await this.request("localhost:8080", xmlPayload, headerRequest, SoapType.product);
if (soap.error) {
reject(soap.error);
}
if (soap.status) {
if (soap.status.firewall.code === '000-000' || soap.status.firewall.code === '000-001') {
resolve(soap);
} else {
reject({ error: soap.status.fireWall.description });
}
} else {
reject({ error: soap });
}
}));
}
public request(uri: string, data: any, headers: IHeaders, type: SoapType): Promise<any> {
return new Promise(((resolve) => {
this.httpSoap.request(uri, data, (async (err, res) => {
if (err) {
resolve({ error: err });
} else {
try {
console.log("fireWall response: "+data);
const bodyJson = await this.xmlUtils.formatXmlToJson(res.body);
const status: StatusInterface = await this.xmlUtils.formatStatusXML(bodyJson);
let body;
if (type === SoapType.product) {
body = await this.xmlUtils.formatproductServiceBodyXml(bodyJson);
this.insertproduct(stringify(bodyJson)); //gravar na mongo
} else if (type === SoapType.UNAVAILABILITY) {
body = await this.xmlUtils.formatImpactServiceBodyToXML(bodyJson);
} else if (type === SoapType.TASK) {
body = await this.xmlUtils.formatTaskServiceBodyXML(bodyJson);
} else {
body = '';
}
const response: ResponseInterface = {
status,
body,
};
resolve(response);
} catch (e) {
resolve(e);
}
}
}), headers);
}));
}
public simpleRequest(connection, payload): Promise<any> {
return new Promise<any>((resolve, reject) => {
const headers = {
};
this.httpSoap.request("localhost:8080", payload, (async (err, res) => {
if (err) {
resolve({ error: err });
} else {
try {
if (res.statusCode === 500) {
// const bodyJson = await this.xmlUtils.formatXmlToJson(res.body);
resolve(res.body);
} else {
const bodyJson = await this.xmlUtils.formatXmlToJson(res.body);
resolve(bodyJson);
}
} catch (e) {
reject(e);
}
}
}), headers);
});
}
}
My goal is to be able to save to mongo and also be able to make the http call to the SOAP api
This warning is shown when you don't add a rejection handler to a Promise, and it's rejected. It can be rejected when an error is occurred inside a promise, or reject() is called.
reject called:
const aa = new Promise((resolve, reject) => {
reject(new Error('whoops'));
});
aa.then(v => {
console.log(v);
});
// Running this script gives unhandled rejection warning
an error is occurred:
const aa = new Promise((resolve, reject) => {
const a = {};
// "cannot read property 'unexistingProperty' of undefined" error is thrown here
const b = a.b.unexistingProperty;
// alternatively, when an is thrown with throw
// throw new Error('oops')
});
aa.then(v => {
console.log(v);
});
// Running this script also gives unhandled rejection warning
You can add a rejection handler via then (the second argument to then() is rejection handler) or catch. For async/await you can add a try/catch block to catch error.
In node.js you can also add rejection handler to all unhandled rejected promises with process.on('unhandledRejection') like this:
process.on('unhandledRejection', error => {
console.log(error);
});
You can also see where the error is thrown with unhandledRejection event handler shown above, or you can run node.js with --trace-warnings like this.
node --trace-warnings index.js
References:
https://thecodebarbarian.com/unhandled-promise-rejections-in-node.js.html
https://nodejs.org/dist/latest-v14.x/docs/api/process.html
Related
Error: function uses multiple asynchronous interfaces: callback and
promise
to use the callback interface: do not return a promise
to use the promise interface: remove the last argument to the function
I'm trying to write a cucumber test to one of my GET node API, and keep getting the above, looked at few GitHub and stack-overflow posts, and could not understand the issue, below are my test method details.
App.ts
async getSsoId(refId: any): Promise<string> {
let ssoId = '';
const secrets = await this.vaultService.getClientSecrets();
this.decrypt(refId, secrets.encryption_secret, secrets.encryption_Id).then((value: any) => {
ssoId = value;
});
return ssoId;
}
api.get('/status', async (req, res) => {
let id;
const statusCode = 200;
try {
id = await this.getId(String('123456'));
} catch (err: any) {
throw new ApiError('Error fetching id');
}
try {
const item = await dbService.getItem(id);
if (item) {
statusCode = 201;
} else {
statusCode = 202;
}
} catch (err: any) {
throw new ApiError(
'The API encountered an error while attempting to communicate with the DB service.'
);
}
res.status(statusCode).send(statusCode);
});
Step Definition:
Given('a valid customer', function () {});
When("I call the get-id api", { timeout: 2 * 5000 }, async function (val) {
util.callGetIdAPI().then((response) => {
this.setApiResponseStatus(response.status);
this.setResponseBody(JSON.stringify(response.body));
});
});
Then("the apiResponseStatus should be <apiResponseStatus>", function (status) {
assert.strictEqual(status, this.apiResponseStatus);
});
Then("the responseBody should be {string}", function (responseBody) {
assert.equal(responseBody, this.responseBody);
});
Util Function
callGetIdAPI = async () => {
const headers = {
'Content-Type': 'application/json;v=1',
Accept: 'application/json;v=1'
}
const client = await getClient('url');
const options = {
method: 'GET',
headers: headers,
version: 3
};
let response;
try {
response = await client.get('/status', options);
return {
status: response.statusCode,
body: response.body
};
} catch(error) {
return {
status: error.statusCode,
body: {
error: {
id: error.id,
message: error.message
}
}
}
}
};
I'm new to this and trying to understand how multiple Premisses and Callbacks works in parallel, any thoughts or inputs on what possibly cause the error, or am I missing anything ??
I am writing test cases for code coverage using typescript. I am using Jasmine spyOn, but it is not covered. But some of the methods are covered and I am not getting any error. I need your guidance to resolve this. I provide below the actual code.
import {IncomingMessage} from "http";
import * as https from "https";
import * as logger from "winston";
export class ApiUtil {
public static async authenticate(params: object): Promise<string> {
let response: Promise<string> = null;
response = new Promise((resolve) => {
try {
const req: any = https.request(params, (res) => {
ApiUtil.getAuthResponse(res).then((resstr) => {
resolve(resstr);
})
.catch((error) => {
logger.info("Internal unexpected authentication error: ", error);
if (error === "NOT_FOUND") {
resolve("NOT_FOUND");
} else {
resolve("Error");
}
});
});
req.on("error", (error: any) => {
logger.info("Internal unexpected authentication error for unknown host: ", error);
resolve("Error");
});
req.end();
} catch (error) {
logger.info("Unexpected Authentication Error: ", error);
logger.info("To DO");
}
});
return response;
}
public static getAuthParams(host: string, userName: string, pwc: string): object {
const base64Credential = ApiUtil.getBase64EncodeCredential(userName, pwc);
logger.info("Base64 Credential successfully received");
const params: object = {
agent: false,
headers: {
Authorization: base64Credential,
accepts: "application/json",
},
hostname: host,
method: "POST",
path: "/LOGIN_REST_URI",
rejectUnauthorized: false,
};
return params;
}
public static async getAuthResponse(res: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
ApiUtil.getRestAPIResponseText(res).then((resstr) => {
resolve(resstr.value);
})
.catch((e) => {
logger.info("Unexpected error while getting authentication response: ", e);
reject(e);
});
});
}
public static async getRestAPIResponseText(res: IncomingMessage): Promise<any> {
return new Promise((resolve, reject) => {
if (res && res.statusCode === 200) {
res.on("data", (dataChunk: string) => {
logger.info("Actual API call response");
const body = JSON.parse(dataChunk);
logger.info("Actual Body received");
resolve(body);
});
} else if (res && res.statusCode === 404) {
reject(res.statusMessage);
} else {
reject(null);
}
});
}
public static getBase64EncodeCredential(userName: string, pwc: string): string {
let b64Encvalue: string = null;
b64Encvalue = "Basic " + new Buffer(userName + ":" + pwc).toString("base64");
return b64Encvalue;
}
public static getApiParams(vmwareSessionId: string, host: string, restApiURI: string): object {
logger.info("vCenter Session Id received");
const options: object = {
headers: {
"vmware-api-session-id": vmwareSessionId,
},
hostname: host,
method: "GET",
path: restApiURI,
rejectUnauthorized: false,
requestCert: true,
};
return options;
}
public static async getApiResponse(vmwareSessionId: string, host: string, restAPIURI: string): Promise<any> {
let doesExist: Promise<any> = null;
const params: object = ApiUtil.getApiParams(vmwareSessionId, host, restAPIURI);
doesExist = new Promise((resolve, reject) => {
try {
const req: any = https.request(params, (res) => {
ApiUtil.getRestAPIResponseText(res).then((resstr) => {
resolve(resstr);
})
.catch((e) => {
logger.info("Inner Unexpected error while calling getApiResponse(): ", e);
reject(null);
});
});
req.on("error", (error: any) => {
logger.info("Internal unexpected authentication error for unknown host: ", error);
resolve(null);
});
req.end();
} catch (error) {
logger.info("Outer Unexpected error while calling getApiResponse(): ", error);
reject(error);
}
});
return doesExist;
}
public static async getVersionApiResponse(vmwareSessionId: string, host: string, restAPIURI: string):
Promise<string> {
let versionDetails: Promise<string> = null;
const params: object = ApiUtil.getApiParams(vmwareSessionId, host, restAPIURI);
versionDetails = new Promise((resolve, reject) => {
try {
const req: any = https.request(params, (res) => {
ApiUtil.getRestAPIResponseText(res).then((resstr) => {
resolve(resstr.value.version);
})
.catch((e) => {
logger.info("Internal Unexpected Error while calling getVersionApiResponse(): ", e);
resolve(null);
});
});
req.on("error", (error: any) => {
logger.info("Internal unexpected authentication error for unknown host: ", error);
resolve(null);
});
req.end();
} catch (error) {
logger.info("Unexpected Error while calling getVersionApiResponse(): ", error);
reject(error);
}
});
return versionDetails;
}
public static async getVersionNBuildApiResponse(vmwareSessionId: string, host: string, restAPIURI: string):
Promise<any> {
const versionNBuild: any = {version: null, build: null};
let versionInfo: Promise<any> = null;
const params: object = ApiUtil.getApiParams(vmwareSessionId, host, restAPIURI);
versionInfo = new Promise((resolve, reject) => {
try {
const req: any = https.request(params, (res) => {
ApiUtil.getRestAPIResponseText(res).then((resstr) => {
versionNBuild.version = resstr.value.version;
versionNBuild.build = resstr.value.build;
resolve(versionNBuild);
})
.catch((e) => {
logger.info("Internal Unexpected Error while calling getVersionApiResponse(): ", e);
versionNBuild.version = null;
versionNBuild.build = null;
resolve(versionNBuild);
});
});
req.on("error", (error: any) => {
logger.info("Internal unexpected authentication error for unknown host: ", error);
versionNBuild.version = null;
versionNBuild.build = null;
resolve(versionNBuild);
});
req.end();
} catch (error) {
logger.info("Unexpected Error while calling getVersionApiResponse(): ", error);
reject(error);
}
});
return versionInfo;
}
}
I am writing below the spec code.
import { ApiUtil } from "./api.util";
describe("API Utility", () => {
it("should not validate authentication", ((done) => {
// spyOn(https, "request").and.returnValue(Promise.resolve("someValue"));
spyOn(ApiUtil, "getBase64EncodeCredential").and.returnValue("someValue1");
spyOn(ApiUtil, "getAuthParams").and.returnValue(Promise.resolve("someValue"));
spyOn(ApiUtil, "getRestAPIResponseText").and.returnValue("clusterName");
spyOn(ApiUtil, "getAuthResponse").and.returnValue(Promise.resolve("someResponse"));
spyOn(ApiUtil, "getApiResponse").and.returnValue("[]");
const params: object = {};
ApiUtil.authenticate(params).then( (response) => {
expect(response).toBe("Error");
done();
});
}), 30000);
});
I also provide below the screenshot which is not covered.
You maybe should have a look here => Code Coverage issue when spyon is used
That said, I'm going to replicate the content here and anticipate a little bit to make it easier.
"Because the spy actually replaces the function call. You have to make two tests : the first will test if the function is actually called (what you did), and the second will test your onNext function" By – user4676340.
So, do ApiUtil.YouFunction() to let it have been called indeed.
I have found out that my and my colleagues lambda doesn't return data we anticipate.
After what I have already found, we used deprecated invokeAsync which returnes only statusCode.
I have upgraded aws-sdk to the newest version and change the invocation code to use invoke with
InvocationType: 'RequestResponse'
as that should give us the returned value.
Lambdas code itself looks good, its an async function without arguments.
Im not using the callback here (3rd argument of handler), as we are doing some async stuff and it would require a small refactor to not use async/await, also i have read that just returning value is ok as well.
Earlier lambda was returning array, but after some investigation and googling I have changed it to
return {
statusCode: 200,
body: JSON.stringify(result)
}
where result is the mentioned array, as this was recurring pattern in search results.
Overall result is in our service where we do the invocation, we get timeout, lambda logs that it is returning value, but then retries itself again and again.
Dumping code
invocation in our service:
const lambda = new AWS.Lambda({ httpOptions: { timeout: 600000 } })
const results = await new Promise((resolve, reject) => {
lambda.invoke(
{
FunctionName: `lambda-name`,
InvocationType: 'RequestResponse',
Payload: '""'
},
(err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}
)
})
lambda handler
import { parallelLimit } from 'async'
const PARALLEL_FETCHERS_NUMBER = 2
export async function runTasksInParallel (
tasks,
limit,
) {
return new Promise(
(
resolve,
reject,
) => {
parallelLimit(tasks, limit, (error, results) => {
if (error === null) {
resolve(results)
} else {
reject(error)
}
})
},
)
}
async function connectToDB(logger) {
const MONGO_URL = process.env.MONGO_URL || 'mongodb://localhost:27017/'
let db
logger.debug('Connecting to mongo')
try {
db = await MongoClient.connect(
MONGO_URL,
{
sslValidate: false,
}
)
} catch (error) {
logger.error('Could not connect to Mongo', { error })
throw error
}
logger.debug('Connected')
return db
}
async function fetchAndStore(
list,
sources,
logger
) {
const results = []
const tasks = sources.map((source) => async (callback) => {
const { fetchAdapter, storageAdapter } = source
try {
const { entries, timestamp } = await fetchAdapter.fetchLatest(list)
await storageAdapter.storeMany(timestamp, entries)
results.push(['fetch.success', 1, [ `fetcher:${fetchAdapter.name}` ] ])
} catch (error) {
const errorMessage = `Failed to fetch and store from adapter ${fetchAdapter.name}`
const { message, name, stack } = error
logger.error(errorMessage, { error: { message, name, stack } })
results.push(['fetch.error', 1, [ `fetcher:${fetchAdapter.name}` ] ])
}
callback && callback(null)
})
await runTasksInParallel(tasks, PARALLEL_FETCHERS_NUMBER)
return results
}
export async function handle() {
const logger = createLambdaLogger() // just a wrapper for console.log to match interface in service
logger.debug('Starting fetching data')
const db: Db = await connectToDB(logger)
const primaryCollection = db.collection(PRIMARY_COLLECTION)
const itemsColletion = db.collection(ITEMS_COLLECTION)
const secondaryCollection = db.collection(SECONDARY_PRICING_COLLECTION)
const primaryStorageAdapter = new StorageAdapter(
Promise.resolve(primaryCollection),
logger
)
const itemsStorageAdapter = new ItemsStorageAdapter(
Promise.resolve(itemsColletion),
logger
)
const secondaryStorageAdapter = new StorageAdapter(
Promise.resolve(secondaryCollection),
logger
)
const primaryFetchAdapter = new PrimaryFetchAdapter(getFetcherCredentials('PRIMARY'), logger)
const secondaryFetchAdapter = new SecondaryFetchAdapter(getFetcherCredentials('SECONDARY'), logger)
const sources = [
{ fetchAdapter: primaryFetchAdapter, storageAdapter: primaryStorageAdapter },
{ fetchAdapter: secondaryFetchAdapter, storageAdapter: secondaryStorageAdapter },
]
try {
const list = await itemsStorageAdapter.getItems()
logger.debug(`Total items to fetch ${list.length}`)
const result = await fetchAndStore(list, sources, logger)
logger.debug('Returning: ', { result })
return {
statusCode: 200,
body: JSON.stringify(result)
}
} catch (error) {
const errorMessage = 'failed to do task'
const { message, name, stack } = error
logger.error(errorMessage, { error: { message, name, stack } })
return {
statusCode: 500,
body: JSON.stringify(new Error(errorMessage))
}
} finally {
await db.close()
}
}
Edit:
I have changed the way i'm invoking the lambda to this below. Basically I tried to listen for every possible event that can say me something. Result is that i get a error event, with response message being Converting circular structure to JSON. Thing is I don't really know about which structure this is, as the value I'm returning is a two elements array of 3 elements (strings and number) arrays.
const lambda = new AWS.Lambda({
httpOptions: { timeout: 660000 },
maxRetries: 0
})
const request = lambda.invoke(
{
FunctionName: `${config.get('env')}-credit-rubric-pricing-fetching-lambda`,
InvocationType: 'RequestResponse',
Payload: '""'
},
(err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}
)
const results = await request
.on('send', () => {
logger.debug('Request sent to lambda')
})
.on('retry', response => {
logger.debug('Retrying.', { response })
})
.on('extractError', response => {
logger.debug('ExtractError', { response })
})
.on('extractData', response => {
logger.debug('ExtractData', { response })
})
.on('error', response => {
logger.debug('error', { response })
})
.on('succes', response => {
logger.debug('success', { response })
})
.on('complete', response => {
logger.debug('complete', { response })
})
.on('httpHeaders', (statusCode, headers, response, statusMessage) => {
logger.debug('httpHeaders', { statusCode, headers, response, statusMessage })
})
.on('httpData', (chunk, response) => {
logger.debug('httpData', { response, chunk })
})
.on('httpError', (error, response) => {
logger.debug('httpError', { response, error })
})
.on('httpDone', response => {
logger.debug('httpDone', { response })
})
.promise()
I'm writing a test for some code that will use Promise.race to bring back a result from a graphql service that is on (could be on) multiple servers. I've used Nock to mock the request, which works fine when I'm hitting a single service. When I mock up multiple services, Nock throws an error saying
AssertionError: expected [Function] to not throw an error but 'Error: Error: Nock: No match for request {\n "method": "POST",\n "url": "http://94.82.155.133:35204",\n "headers": {\n "content-type": "application/json",\n "accept": "application/json"\n },\n "body": "{...}"\n}' was thrown
my test looks like this:
it('should make two POST requests to the service for data from graphQL', async () => {
const spy = sinon.spy(releases, '_queryGraphQL');
const releaseID = 403615894;
nock.cleanAll();
const services = serviceDetails(NUMBER_OF_SERVICES); // NUMBER_OF_SERVICES = 3
nock(serviceDiscoveryHost)
.get('/v1/catalog/service/state51')
.reply(HTTP_CODES.OK, services);
for (const service of services) {
const currentNodeHealth = nodeHealth(service.Node);
nock(serviceDiscoveryHost)
.get('/v1/health/node/'+service.Node)
.reply(HTTP_CODES.OK, currentNodeHealth);
const delayTime = Math.floor(Math.random()*1000);
nock('http://'+service.Address+':'+service.ServicePort, serviceHeaders)
.post('/')
.delay(delayTime)
.replyWithError({code: 'ETIMEDOUT', connect: false})
.post('/')
.delay(delayTime)
.reply(HTTP_CODES.OK, getReply(releaseID))
}
const actual = await releases.getRelease(releaseID)
.catch((err) => {
console.log(releases._retries);
(() => { throw err; }).should.not.throw();
});
expect(releases._retries[releaseID]).to.be.equal(1);
expect(spy.callCount).to.be.equal(2);
expect(actual).to.be.an('object')
expect(actual.data.ReleaseFormatById.id).to.be.equal(releaseID);
});
and the offending bit of code looks like
async _queryGraphQL(releaseID, services) {
if (! this._retries[releaseID]) {
this._retries[releaseID] = 0;
}
const postData = this._getReleaseQuery(releaseID);
return Promise.race(services.map( (service) => {
const options = this._getHTTPRequestOptions(service);
return new Promise((resolve, reject) => {
let post = this.http.request(options, (res) => {
let data = '';
if (res.statusCode < 200 || res.statusCode > 299) {
const msg = this.SERVICE_NAME + ' returned a status code outside of acceptable range: ' + res.statusCode;
reject(new QueryError(msg, postData));
} else {
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('error', (err) => {
reject(new QueryError(err.message, postData, err));
});
res.on('end', () => {
resolve(JSON.parse(data));
});
}
});
post.on('error', async (err) => {
if (err.code === 'ETIMEDOUT') {
if (this._retries[releaseID] &&
this._retries[releaseID] === 3) {
reject(err);
} else {
this._retries[releaseID] += 1;
resolve(this._queryGraphQL(releaseID, services));
}
} else {
reject(new QueryError(err.message, postData, err));
}
});
post.write(JSON.stringify(postData));
post.end();
});
}));
}
this.http is just require('http');. and the options will be {hostname: service.hostname} \\ example.com etc.
What I'm expecting, is that if the first service to respond, responds with an error relating to: 'ETIMEDOUT', it'll recall the function (upto 2 more times) and try all the services again until the first service to respond is something that isn't a 'ETIMEDOUT'.
I'm getting this error every time I press a specific button on my react-native app. I'm guessing it has to do something with an Api call or maybe it is trying to invoke URLs with localhost:8081, which may not be correct as it should be pointing to a server. I'm not sure exactly how to pinpoint the problem. Any help would be much appreciated. I'm also now sure if the problem is coming from the file I shared underneath.
App Warning message:
This is my RestClient code:
import {Alert, AsyncStorage} from 'react-native'
import {resetRouteTo} from 'util/NavigationHelper'
import {store} from '../index.js'
import {dropdownAlert} from 'util/AlertManager'
const DEFAULT_ERROR = {error: {code: 500, message: 'No JSON message'}}
export default class RestClient {
constructor (baseUrl = '', navigation, { headers = {}, devMode = false, simulatedDelay = 0 } = {}) {
if (!baseUrl) throw new Error('missing baseUrl')
this.navigation = navigation
this.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
Object.assign(this.headers, headers)
this.baseUrl = baseUrl
this.simulatedDelay = simulatedDelay
this.devMode = devMode
}
_clearTokenAndGo () {
AsyncStorage.removeItem('apiToken')
Alert.alert(
'Error',
'Something went wrong and your account needs to be re-instantiated.',
[
{text: 'OK', onPress: () => resetRouteTo(this.navigation, 'OnboardingFirst')}
],
{ cancelable: false }
)
}
_simulateDelay () {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, this.simulatedDelay)
})
}
async _parseIfJson (response) {
var contentType = response.headers.get('content-type')
if (contentType && contentType.indexOf('application/json') !== -1) {
return response.json()
}
return null
}
async _handleError (response) {
const body = await this._parseIfJson(response)
if (!body) {
dropdownAlert('error', `Server Error ${response.status}`, 'Something went wrong on the server')
return DEFAULT_ERROR
}
switch (response.status) {
case 200: {
break
}
case 401: {
if (body.error === 'Unauthenticated.') {
this._clearTokenAndGo()
}
break
}
case 400: {
dropdownAlert('error', `Error ${body.error.code}`, body.error.message)
break
}
default: {
if (body.error) {
dropdownAlert('error', `Error ${body.error.code}`, body.error.message)
} else {
dropdownAlert('error', 'Error', `An unknown error has occurred. Http status ${response.status}`)
}
break
}
}
return body
}
_fullRoute (url) {
return `${this.baseUrl}${url}`
}
async _fetch (route, method, body, isQuery = false) {
if (!route) throw new Error('Route is undefined')
if (!store.getState().netinfo.isConnected) {
this.navigation.navigate('BlockScreen')
return {success: false, error: {code: 1, message: 'No internet connection.'}}
}
var fullRoute = this._fullRoute(route)
if (isQuery && body) {
var qs = require('qs')
const query = qs.stringify(body)
fullRoute = `${fullRoute}?${query}`
body = undefined
}
let opts = {
method,
headers: this.headers
}
if (body) {
Object.assign(opts, { body: JSON.stringify(body) })
}
const fetchPromise = () => fetch(fullRoute, opts)
if (this.devMode && this.simulatedDelay > 0) {
// Simulate an n-second delay in every request
return this._simulateDelay()
.then(() => fetchPromise())
.then(response => response.json())
} else {
let promise = await fetch(fullRoute, opts)
console.log('Logging response =>')
console.log(promise)
return this._handleError(promise)
}
}
GET (route, query) { return this._fetch(route, 'GET', query, true) }
POST (route, body) { return this._fetch(route, 'POST', body) }
PUT (route, body) { return this._fetch(route, 'PUT', body) }
DELETE (route, query) { return this._fetch(route, 'DELETE', query, true) }
}
Other files using RestClient:
import RestClient from 'util/RestClientLib'
import qs from 'qs'
import { Linking, AsyncStorage } from 'react-native'
import Config from 'react-native-config'
var SHA256 = require('crypto-js/sha256')
export default class ApiClient extends RestClient {
constructor (authToken, navigation) {
console.log('constructing apiClient with base: ', Config.API_URL)
super(Config.API_URL, navigation, {
headers: {
'Authorization': 'Bearer ' + authToken
}
})
}
_switchSchemeTo (url, scheme) {
var split = url.split(':')
split[0] = scheme
return split.join(':')
}
_makeAppUrl (url) {
const prefix = url.split('.')[0]
const bank = prefix.substr(prefix.lastIndexOf('/') + 1, prefix.length)
switch (bank) {
case 'florijnbank':
return this._switchSchemeTo(url, 'flrb')
default:
return url
}
}
async _openUrl (url) {
const openInApp = await AsyncStorage.getItem('openInApp')
const appUrl = this._makeAppUrl(url)
Linking.canOpenURL(appUrl).then(supported => {
if (!supported || openInApp !== 'true') {
Linking.canOpenURL(url).then(supported => {
if (!supported) {
console.log('Can\'t handle url: ' + url)
} else {
Linking.openURL(url)
}
}).catch(err => console.error('An error occurred', err))
} else {
Linking.openURL(appUrl)
}
}).catch(err => console.error('An error occurred', err))
}
async createToken (pin) {
var hash = SHA256(pin).toString()
const query = {pincode: hash}
let response = await this.POST('/user', query)
return response.api_token
}
async checkPin (pin) {
const hash = SHA256(pin).toString()
const query = {pincode: hash}
return this.GET('/user/validate', query)
}
getAccounts () {
return this.GET('/account')
}
getBalance (iban) {
return this.GET(`/account/${iban}`)
}
async getPermissionBank (bank) {
const query = {
bank_id: bank
}
let response = await this.POST('/account', query)
return this._openUrl(response.url)
}
postCredentialsAis (form) {
return this.POST('/account/password', form)
}
async ais (iban) {
let response = await this.GET(`/ais/${iban}`)
if (response.url == null) {
return true
}
this._openUrl(response.url)
return false
}
getTransactionDetails (requestId) {
return this.GET(`/request/${requestId}`)
}
getTransactions (iban) {
return this.GET(`/account/${iban}/transaction`)
}
getContacts () {
return this.GET('/contacts')
}
getSettings () {
return this.GET('/startup')
}
deleteAccount (iban) {
return this.DELETE(`/account/${iban}`)
}
/**
* async submitTransfer - submits a transfer
*
* #param {Object} options object which holds the pin, iban, sso and query
* #param {Object} transfer hold the transfer information
* #return {Boolean} either opens a link or a boolean for success
*/
submitTransfer (iban, credentials, transfer) {
const url = `/account/${iban}/transaction?${qs.stringify(credentials)}`
const body = {
counter_iban: transfer.counterIban,
counter_account_name: transfer.name,
amount: transfer.amount,
description: transfer.description,
reference: transfer.reference
}
return this.POST(url, body)
}
qrConfirmTransfer (transactionId, iban, credentials) {
const url = `/request/${transactionId}/confirm/${iban}?${qs.stringify(credentials)}`
return this.POST(url)
}
directConfirmTransfer (transactionId, iban, form) {
const url = `/account/${iban}/transaction/${transactionId}`
return this.POST(url, form)
}
verificationRequired (iban, amount) {
const query = {amount: amount}
return this.GET(`/veriReq/${iban}`, query)
}
};