I am trying to setup PouchDB to sync between browser (React + Redux stack) and my databases behind a node proxy (which checks authentication using JWT). here is my proxy code:
this.app.all('/database/*', bodyParser.urlencoded({extended: true}), bodyParser.json(), (req, res) => {
// req.pause();
const headers = this.auth.proxyRequestDecorator()({headers: {...req.headers}}, req);
const remoteUrl = req.url.slice(10);
const opts = Object.assign({
method: req.method,
url: `http://${config.get('couchHost')}:${config.get('couchPort')}/${remoteUrl}`,
...headers,
}, req.method !== 'GET' ? {body: JSON.stringify(req.body)} : {});
mainStory.info('http', `${req.method} ${req.url} -> http://${config.get('couchHost')}:${config.get('couchPort')}/${remoteUrl}`, {
attach: opts,
attachLevel: 'trace'
});
const remoteReq = request(opts).pipe(res);
});
Here is the code I use to sync the database in the browser:
function syncDB (dbs: string[], max: number): thunk.ThunkAction<void, Defs.CompactdState, void> {
return (dispatch, getState) => {
const dbName = dbs[0];
const db = new PouchDB(dbName);
const remote = getDatabase(dbName);
db.sync(remote).on('complete', (info) => {
dispatch({
type: UPDATE_SYNC,
progress: (max - dbs.length + 1) / max
});
db.sync(remote, {live: true}).on('change', (info) => {
console.log(info);
}).on('error', (err) => {
console.log(dbName, err);
}).on('paused', function (info) {
console.log(dbName+' pause', info);
});
if (dbs.length > 1) {
return (syncDB(dbs.slice(1), max) as any)(dispatch, getState);
} else {
setTimeout(() =>
dispatch({
type: END_SYNC
}), 250);
}
});
}
}
function sync (): thunk.ThunkAction<void, Defs.CompactdState, void> {
return (dispatch, getState) => {
if (getState().app.syncing) {
return;
}
const dbs = [ 'artists', 'albums', 'tracks', 'files', 'trackers'];
(syncDB(dbs, dbs.length) as any)(dispatch, getState);
}
}
But when in the browser I create a new tracker document, it doesn't get synceed and if I clear site data, and reload the page the new document doesn't exists (nor in the couchdb ui).
Here is the log :
trackers pause undefined
trackers pause undefined
artists CustomPouchError {code: "ETIMEDOUT", status: 0, result: {…}}
albums CustomPouchError {code: "ETIMEDOUT", status: 0, result: {…}}
tracks CustomPouchError {code: "ETIMEDOUT", status: 0, result: {…}}
files CustomPouchError {code: "ETIMEDOUT", status: 0, result: {…}}code: "ETIMEDOUT"result: {ok: false, start_time: Sun Sep 17 2017 19:58:29 GMT+0200 (Paris, Madrid (heure d’été)), docs_read: 0, docs_written: 0, doc_write_failures: 0, …}status: 0__proto__: Error
trackers CustomPouchError {code: "ETIMEDOUT", status: 0, result: {…}}
It also blocks the page from reloading when f5 isnpressed until connection times out
Related
Push notification node js API using One signal
Hello guys, I've watched a tutorial to implement push notifications on flutter app project.
the code I'll show is how to set up a push notification on node js API using one signal.
I need help to know how to view the notification using One Signal API.
here is the notification service folder
notification.services.js
const { ONE_SIGNAL_API_KEY } = require('../utils/config')
const { info } = require('../utils/logger')
const sendNotification = async (data, callback) => {
const headers = {
'Content-Type': 'application/json; charset=utf-8',
Authorization: 'Basic ' + ONE_SIGNAL_API_KEY,
}
const options = {
host: 'onesignal.com',
port: 443,
path: '/api/v1/notifications',
method: 'POST',
headers: headers,
}
const https = require('https')
const req = https.request(options, res => {
res.on('data', data => {
info(JSON.parse(data))
return callback(null, JSON.parse(data))
})
})
req.on('error', e => {
return callback({
message: e,
})
})
req.write(JSON.stringify(data))
req.end()
}
here is the notification controller folder
notification.controller.js
const { ONE_SIGNAL_APP_ID } = require('../utils/config')
const notificationsService = require('../services/notifications.services')
const sendNotification = (req, res, next) => {
const message = {
app_id: ONE_SIGNAL_APP_ID,
headings: { en: 'All Devices' },
contents: { en: 'Send push notifications to all devices' },
included_segments: ['All'],
content_available: true,
small_icon: 'ic_notification_icon',
data: {
// eslint-disable-next-line quotes
PushTitle: "Porc'Ivoire",
},
}
notificationsService.sendNotification(message, (error, results) => {
if (error) {
next(error)
}
return res.status(200).send({
message: 'Success',
data: results,
})
})
}
const sendNotificationToDevice = (req, res, next) => {
var message = {
app_id: ONE_SIGNAL_APP_ID,
headings: { en: '🤑 Paiement accepté' },
contents: {
en: 'Votre paiment a été effrctué avec succès',
},
included_segments: ['included_player_ids'],
include_player_ids: req.body.devices,
content_available: true,
small_icon: 'ic_notification_icon',
data: {
// eslint-disable-next-line quotes
PushTitle: "Porc'Ivoire",
},
}
notificationsService.sendNotification(message, (error, results) => {
if (error) {
next(error)
}
return res.status(200).send({
message: 'Success',
data: results,
})
})
}
module.exports = {
sendNotification,
sendNotificationToDevice,
}
I'm unable to get axios-retry to work at all no matter where I put the code. Can someone help out?
I want to be able to wait and retry in case of rate-limiting or any other errors.
For testing purposes, I put a bad API key in to force a 403 and all I got was the following:
Error 403 - AxiosError: Request failed with status code 403
No attempts at a retry.
Thanks!
async function getRecipientValidation (emaillist) {
axiosRetry(axios, {
retries: 3, // number of retries
retryDelay: (retryCount) => {
console.log(`retry attempt: ${retryCount}`);
return retryCount * 20000; // time interval between retries
},
retryCondition: (error) => {
// if retry condition is not specified, by default idempotent requests are retried
return error.response.status === 503;
},
});
var completed = 0
try {
const promises = emaillist.map(async email => {
const response = await axios({
method: 'GET',
url: 'https://api.sparkpost.com/api/v1/recipient-validation/single/' + email,
headers: {
'Authorization': API_KEY
}
})
.catch(error =>
{
console.log("Error " + error.response.status + " - " + error)
})
completed++
process.stdout.write("Done with " + completed + " / " + emaillist.length + "\r")
return {
email: email,
valid: response.data.results.valid,
result: response.data.results.result,
is_role: response.data.results.is_role,
is_disposable: response.data.results.is_disposable,
is_free: response.data.results.is_free,
delivery_confidence: response.data.results.delivery_confidence,
}
})
const results = await Promise.all(promises)
csvWriter.writeRecords(results)
console.log("Emails Completed Validation")
} catch (err) {
/**
* If the request is rejected, then the catch method will be executed.
*/
}
};
const axios = require('axios');
const axiosRetry = require('axios-retry');
const csv = require('fast-csv');
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
const fs = require('fs');
//const API_KEY = ''
const API_KEY = ''
const csvWriter = createCsvWriter({
path: 'out.csv',
header: [
{id: 'email', title: 'Email'},
{id: 'result', title: 'Result'},
{id: 'valid', title: 'Valid'},
{id: 'is_role', title: 'Is_Role'},
{id: 'is_disposable', title: 'Is_Disposable'},
{id: 'is_free', title: 'Is_Free'},
{id: 'delivery_confidence', title: 'Delivery_Confidence'},
]
});
let emaillist = []
fs.createReadStream('valtest.csv')
.pipe(csv.parse({headers: false}))
.on('data', (row) => {
emaillist.push(row)
})
.on('end', () => {
getRecipientValidation(emaillist);
});
You have to set the retryCondition property of axios-retry to trigger on 403. By default, it only retries isNetworkOrIdempotentRequestError, which means NETWORK_ERROR or 5XX status code, hence your 403 not triggering a retry. See the documentation here for more details.
axiosRetry(axios, {
retries: 3,
retryDelay: (retryCount) => {
console.log(`retry attempt: ${retryCount}`);
return retryCount * 20000;
},
retryCondition: (error) => {
// here is your mistake. check for error config you want to retry
return error.response.status === 503;
},
});
This project is to record data by AWS Timestream, and it works well.
However, I'm failed to mock AWS TimestreamWrite by using jest. I tried some ways but not working. Can someone help me?
My files as below:
ledger-service.js
const AWS = require("aws-sdk");
const enums = require("./enums");
var https = require("https");
var agent = new https.Agent({
maxSockets: 5000,
});
const tsClient = new AWS.TimestreamWrite({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: agent,
},
});
module.exports = {
log: async function (audit) {
try {
if (Object.keys(audit).length !== 0) {
if (!isPresent(audit, "name")) {
throw new Error("Name shouldn't be empty");
}
if (!isPresent(audit, "value")) {
throw new Error("Value shouldn't be empty");
}
return await writeRecords(recordParams(audit));
} else {
throw new Error("Audit object is empty");
}
} catch (e) {
throw new Error(e);
}
},
};
function isPresent(obj, key) {
return obj[key] != undefined && obj[key] != null && obj[key] != "";
}
function recordParams(audit) {
const currentTime = Date.now().toString(); // Unix time in milliseconds
const dimensions = [
// { Name: "client", Value: audit["clientId"] },
{ Name: "user", Value: audit["userId"] },
{ Name: "entity", Value: audit["entity"] },
{ Name: "action", Value: audit["action"] },
{ Name: "info", Value: audit["info"] },
];
return {
Dimensions: dimensions,
MeasureName: audit["name"],
MeasureValue: audit["value"],
MeasureValueType: "VARCHAR",
Time: currentTime.toString(),
};
}
function writeRecords(records) {
try {
const params = {
DatabaseName: enums.AUDIT_DB,
TableName: enums.AUDIT_TABLE,
Records: [records],
};
return tsClient.writeRecords(params).promise();
} catch (e) {
throw new Error(e);
}
}
ledger-service.spec.js
const AWS = require("aws-sdk");
const audit = require("./ledger-service");
describe("ledger-service", () => {
beforeEach(async () => {
jest.resetModules();
});
afterEach(async () => {
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit={
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
const mockWriteRecords = jest.fn(() =>{
console.log('mock success')
return { promise: ()=> Promise.resolve()}
});
const mockTsClient={
writeRecords: mockWriteRecords
}
jest.spyOn(AWS,'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(()=>mockTsClient);
//a=new AWS.TimestreamWrite();
//a.writeRecords(); //these two lines will pass the test and print "mock success"
await audit.log(mockAudit); //this line will show "ConfigError: Missing region in config"
expect(mockWriteRecords).toHaveBeenCalled();
});
});
I just think the the AWS I mocked doesn't pass into the ledger-service.js. Is there a way to fix that?
Thanks
updates: Taking hoangdv's suggestion
I am thinking jest.resetModules(); jest.resetAllMocks(); don't work. If I put the "It should write records when all success" as the first test, it will pass the test. However, it will fail if there is one before it.
Pass
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
Failed
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
In ledger-service.js you call new AWS.TimestreamWrite "before" module.exports, this means it will be called with actual logic instead of mock.
The solution is just mock AWS before you call require("./ledger-service");
ledger-service.spec.js
const AWS = require("aws-sdk");
describe("ledger-service", () => {
let audit;
let mockWriteRecords;
beforeEach(() => {
mockWriteRecords = jest.fn(() => {
return { promise: () => Promise.resolve() }
});
jest.spyOn(AWS, 'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(() => ({
writeRecords: mockWriteRecords
}));
audit = require("./ledger-service"); // this line
});
afterEach(() => {
jest.resetModules(); // reset module to update change for each require call
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
});
this is the link of the entire file - asyncActions.js
the part with axios api -
const fetchUsers = () => {
return function (dispatch) {
dispatch(fetchUsersRrequest());
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((res) => {
// res.data is the array of users
const users = res.data.map((user) => user.id);
dispatch(fetchUsersSuccess(users));
})
.catch((error) => {
// error.message gives the description of message
dispatch(fetchUsersFaliure(error.message));
});
};
};
Function Output -
{ loading: true, users: [], error: '' }
{
loading: false,
users: [
1, 2, 3, 4, 5,
6, 7, 8, 9, 10
],
error: ''
}
replacing the part with fetch api -
const fetchUsers = () => {
return function (dispatch) {
dispatch(fetchUsersRrequest());
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => {
const users = res.json().map((user) => user.id);
console.log(users);
dispatch(fetchUsersSuccess(users));
})
.catch((error) => {
dispatch(fetchUsersFaliure(error.message));
});
};
};
the OUTPUT -
{ loading: true, users: [], error: '' }
{
loading: false,
users: [],
error: 'res.json(...).map is not a function'
}
what am I doing wrong ? why can't I map over the data ?
Call res.json() will return a Promise. You need to add a second then block:
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((res) => {
const users = res.map((user) => user.id);
console.log(users);
dispatch(fetchUsersSuccess(users));
})
.catch((error) => {
dispatch(fetchUsersFaliure(error.message));
});
I am using hapijs v17.2.3 . Also I am very new to hapi .I am trying to test my code using Lab a simple test utility for Node.js and code assertion library.
my test.js file is :
'use strict';
var path = require('path');
var dotEnvPath = path.resolve('./.env');
require('dotenv').config({ path: dotEnvPath });
const Code = require('code');
const Lab = require('lab');
const lab = exports.lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const expect = Code.expect;
const Joi = require('joi');
const Hapi = require('hapi');
const app = require('../app');
const server = new Hapi.Server();
const getServer = async () => {
const server = new Hapi.Server();
// server.connection();
return server.register(app)
.then(() => server);
};
lab.experiment('signup testing in "/signup"', () => {
lab.test('Return true if the user can successfully signup', (done, flags) => {
const signUpData = {
method: 'POST',
url: '/signup',
payload: {
name: 'vulcan',
password: 'vulcan#123',
email: 'vulcan#gmail.com',
username: 'vulcan123',
dob: '12-08-1994'
}
};
getServer()
.then((server) => server.inject(signUpData))
.then((response) => {
flags.note(`demo test note`);
if (response) {
console.log(response.statusCode);
Code.expect(response.statusCode).to.equal(201);
Code.expect(payload).to.contain(['name', 'password', 'email', 'username', 'dob']);
}
done();
});
});
});
lab.experiment('1) login test ', () => {
lab.test('login has successfully done', (done) => {
const loginData = {
method: 'POST',
url: '/login',
payload: {
email: 'wrong email',
login_password: 'wrong password',
}
};
getServer()
.then((server) => {
server.inject(loginData)
})
.then((response) => {
Code.expect(response.statusCode).to.equal(200);
done();
});
});
});
my test command is : lab --assert code --coverage -t 100
my signup controller is :
exports.postForm = {
description: 'Submit the signup page',
tags: ['api'],
notes: 'accepts name password verify and email',
auth: {
mode: 'try',
strategy: 'session'
},
validate: {
payload: {
name: Joi.string().required(),
password: Joi.string().min(4).max(20).required(),
verify: Joi.string().required(),
email: Joi.string().email().required(),
username: Joi.string().min(3).max(20).required(),
referredBy: Joi.any(),
dob: Joi.date().required().label('Date of Birth')
},
failAction: (request, h, error) => {
console.log('Validation Failed');
request.yar.flash('error', error.details[0].message.replace(/['"]+/g, ''));
return h.redirect('/signup').takeover();
}
},
handler: async (request, h) => {
try {
var user = {
name: request.payload.name,
password: request.payload.password,
email: request.payload.email,
username: request.payload.username.toLowerCase(),
referralName: request.payload.username + '#gg',
emailConfirmationToken: uuidv1(),
dob: request.payload.dob,
tnc: true
};
let data = await signupHelper.signUpUser(user, request);
if (data.statusCode === 201) {
if (request.payload.referredBy) {
let configureReferral = await signupHelper.configureReferral(request.payload.referredBy, data.userId);
if (configureReferral.statusCode === 200) {
request.yar.flash('success', 'Account created, Please Login');
return h.redirect('/login');
}
}
request.yar.flash('success', 'Account created, Please Login');
return h.redirect('/login');
} else {
request.yar.flash('error', data.message);
return h.redirect('/signup');
}
} catch (error) {
logger.error(error);
return h.redirect('/signup');
}
}
};
my login control :
exports.login = {
description: 'Post to the login page',
notes: 'Accepts two paramters email and password which got validation',
tags: ['api'],
auth: {
mode: 'try',
strategy: 'session'
},
plugins: {
crumb: {
key: 'crumb',
source: 'payload',
},
'hapi-auth-cookie': {
redirectTo: false
}
},
validate: {
payload: {
email: Joi.string().min(3).email().required(),
login_password: Joi.string().min(4).required()
},
failAction: (request, h, error) => {
request.yar.flash('error', error.details[0].message.replace(/['"]+/g, ''));
return h.redirect('/login').takeover();
}
},
handler: async (request, h) => {
try {
const next = request.query.next ? request.query.next : '/dashboard';
if (request.auth.isAuthenticated) {
return h.redirect(next);
}
let resultData = await loginHelper.findByCredentials(request.payload.email, request.payload.login_password);
if (resultData.statusCode === 200) {
request.cookieAuth.set(resultData.user);
return h.redirect(next);
} else {
request.yar.flash('error', resultData.message);
return h.redirect('/login');
}
} catch (error) {
logger.error(error);
request.yar.flash('error', error.message);
return h.redirect('/login');
}
}
};
this is the error when I run the test:
Socket server start initiated
Socket server started
Server started at https://127.0.0.1:8000
signup testing in "/signup"
✔ 1) Return true if the user can successfully signup (3615 ms)
1) login test
✖ 2) login has successfully done
(node:9708) UnhandledPromiseRejectionWarning: AssertionError [ERR_ASSERTION]: Invalid plugin options {
"plugin": {
"sock": {
"init": function (server, options) {\n
..........
..........
},
"register" [1]: -- missing --
}
}
[1] "register" is required
at new AssertionError (internal/errors.js:102:11)
at Object.exports.assert (/home/jeslin/projects/hapi/gamergully/node_modules/hapi/node_modules/hoek/lib/index.js:517:11)
at Object.exports.apply (/home/jeslin/projects/hapi/gamergully/node_modules/hapi/lib/config.js:22:10)
at internals.Server.register (/home/jeslin/projects/hapi/gamergully/node_modules/hapi/lib/server.js:410:31)
at getServer (/home/jeslin/projects/hapi/gamergully/test/tests-signup.js:23:19)
at lab.test (/home/jeslin/projects/hapi/gamergully/test/tests-signup.js:115:9)
at Immediate.setImmediate [as _onImmediate] (/home/jeslin/projects/hapi/gamergully/node_modules/lab/lib/runner.js:628:31)
at runCallback (timers.js:810:20)
at tryOnImmediate (timers.js:768:5)
at processImmediate [as _immediateCallback] (timers.js:745:5)
(node:8764) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 6)
(node:8764) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Mongo Database connected
when I run only one test case, it wont return any error. If I run more
than one this error is showing
I have done this by following this link
This could be related to your server instance creation. In one test you are trying to create more than one server instance with the same configuration. That might be the problem I think. When I create tests, I am using lab.before and lab.beforeEach methods to create instances.
Here is a real-world test case from one of my projects.
const Lab = require('lab');
const lab = exports.lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const before = lab.before;
const after = lab.after;
const expect = require('code').expect;
// ..... other stufff
describe('Test Routes', () => {
let server;
before(async () => {
server = new Hapi.Server();
await server.register(app)
});
after(async () => {
await server.stop();
});
it('It should obtain valid response', async () => {
const qs = querystring.stringify(queryParams);
const res = await server.inject({
url: `/s?${qs}`,
method: 'get',
headers: {
"Cookie": "user=aighaeshaighaPopaGoochee8ahlei8x"
}
});
expect(res.statusCode).to.equal(200);
expect(res.result.userid).to.exist();
expect(res.result.status).to.equal(true);
// handle play action
const res2 = await server.inject({
url: `/b?id=${res.result.userid}`,
method: 'get'
});
expect(res2.statusCode).to.equal(200);
expect(res2.result.status).to.equal(true);
});
//
it('It should reject invalid request', async () => {
const res = await server.inject({
url: `/a?a=b&id=iChah3Ahgaaj2eiHieVeem6uw2xaiD5g`,
method: 'get'
});
expect(res.statusCode).to.equal(200);
expect(res.result.status).to.equal(false);
expect(res.result.message).to.equal("Invalid information");
});
// ... goes on
});
Just pay attention to before and after calls. I am creating only one instance of server then using it along side my test cases or use beforeEach and afterEach to isolate your instances, it's your choice.