How to wait for API response - api

I'm trying to make my API request wait for API response, i did this:
export async function foldercreator(language,date){
const paths = ["" ,"/" + language , "/" + language + "/" + "/kanban","/" + language + "/" + "/LavagnaFornitore", "/" + language + "/" + "/LavagnaPianificatore"];
for(let i=0; i<paths.length; i++){
var request = await require('request');
var options = {
'method': 'MKCOL',
'url': "****"
'headers': {
'Authorization': 'Basic *******',
},
};
await request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
}

Because you don't have async function

Related

Invalid client Zoom Post request

I try to get acces token from the zoom api with a post request but i don't understand why i have this error:
reason: 'Invalid client_id or client_secret',
error: 'invalid_client'
My code :
let clientID = "exemple"
let clientSecret = "exemple";
const zoomtokenep = "https://zoom.us/oauth/token";
const myappredirect = "http://localhost:3000/campaigns";
let url = zoomtokenep + "?grant_type=authorization_code&code=" + "code_exemple" +
"&redirect_uri=" + myappredirect;
let auth = "Basic " + new Buffer(clientID + ':' + clientSecret).toString('base64');
await axios({
method: "POST",
url: url,
headers: {
"Autorization": auth,
},
})
.then(res => console.log(res))
.catch(err => console.error(err));

How to use Spotify 30sec previews with Expo React native app

I have been trying to use the Spotify API in my expo app but every tutorial or wrapper I find doesn't seem to work.
I would specifically like to access the 30-second song previews and track/song searching features.
If anyone could provide some guidance or point me towards a working demo of any kind that would be awesome.
Thanks!
Found parts of the solution in https://docs.expo.dev/guides/authentication/#spotify
const discovery = {
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
tokenEndpoint: 'https://accounts.spotify.com/api/token',
};
var client_id = ''; // Your client id
var client_secret = ''; // Your secret
export default function spotifyLogin(props) {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: '',
scopes: ['user-read-email', 'user-read-playback-state', 'playlist-modify-public','playlist-modify-private','playlist-modify-public','playlist-read-private','user-read-recently-played'],
// In order to follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
// this must be set to false
usePKCE: false,
redirectUri: makeRedirectUri({
//scheme: 'your.app'
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
//save code to local storage
props.saveLogin(code)
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
export const getFirstTokenData = async (code) => {
var dataToSend = {
code: code,
redirect_uri: makeRedirectUri(),
grant_type: 'authorization_code'};
//making data to send on server
var formBody = [];
for (var key in dataToSend) {
var encodedKey = encodeURIComponent(key);
var encodedValue = encodeURIComponent(dataToSend[key]);
formBody.push(encodedKey + '=' + encodedValue);
}
formBody = formBody.join('&');
//POST request
var response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST', //Request Type
body: formBody, //post body
headers: {
//Header Defination
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')),
},
})
try{
return await response.json()
}catch (error){
console.log(error)
}
}
export const getRefreshTokenData = async (refreshToken) => {
console.log(refreshToken)
console.log(refreshToken + " going in for refresh")
var dataToSend = {
refresh_token : refreshToken,
grant_type: 'refresh_token'};
//making data to send on server
var formBody = [];
for (var key in dataToSend) {
var encodedKey = encodeURIComponent(key);
var encodedValue = encodeURIComponent(dataToSend[key]);
formBody.push(encodedKey + '=' + encodedValue);
}
formBody = formBody.join('&');
//POST request
var response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST', //Request Type
body: formBody, //post body
headers: {
//Header Defination
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')),
},
})
try{
return await response.json()
}catch (error){
console.log(error)
}
}
The above takes care of auth and getting refresh tokens, below takes care of searching for a track. To get 30 second previews there is a preview property in the return data for getTrack()
const apiPrefix = 'https://api.spotify.com/v1';
export default async ({
offset,
limit,
q,
token,
}) => {
const uri = `${apiPrefix}/search?type=track&limit=${limit}&offset=${offset}&q=${encodeURIComponent(q)}`;
console.log('search begin, uri =', uri, 'token =', token);
const res = await fetch(uri, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
}
});
const json = await res.json();
//console.log('search got json', json);
if (!res.ok) {
return [];
}
return json
// const {
// tracks: {
// items,
// }
// } = json;
// // const items = json.tracks.items;
// return items.map(item => ({
// id: item.id,
// title: item.name,
// imageUri: item.album.images
// ? item.album.images[0].url
// : undefined
// }));
console.log('search end');
};
export const getTrack = async(trackID, token) => {
const uri = `${apiPrefix}/tracks/${trackID}?market=ES`;
const res = await fetch(uri, {
method: 'GET',
headers: {
// Accept: `application/json`,
// Content-Type: `application/json`,
Authorization: `Bearer ${token}`,
}
});
const json = await res.json();
//console.log('search got json', json);
if (!res.ok) {
return [];
}
return json
}
Once upon a time, I worked on a similar application as a test. It's a bit outdated, but I believe Spotify has not changed its API much in the meantime.
Hope this caa help
https://github.com/kubanac95/spotify-test

Expo React-Native Youtube video upload using Fetch()

I am trying to upload a video on youtube using the V3 Youtube.video.insert API method. When I call the method I get the following error message: Bad request: Request contains an invalid argument.. Despite the error message my upload still appears in my personal YouTube account under My Videos. I am new to React Native and I'm struggling to understand the Youtube API docs, could someone please explain to me what I'm doing wrong or how could I fix it?
This is my current request:
let response = await fetch(
'https://youtube.googleapis.com/youtube/v3/videos?key=' + API_KEY,
{
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
part: 'id,snippet,status',
notifySubscribers: false,
requestBody: {
snippet: {
title: 'YouTube Upload Test',
description: 'Testing YouTube upload',
},
status: {
privacyStatus: 'private',
},
},
media: {
body: 'file:///data/user/0/host.exp.exponent/cache/ExperienceData/Camera/video.mp4',
}
})
}
);
I tried taking everything out from body: but I got the same response.
Here are the links that I am using trying to understand:
https://developers.google.com/youtube/v3/docs/videos/insert
https://github.com/googleapis/google-api-nodejs-client/blob/master/samples/youtube/upload.js
UPDATE:
Ok, I think I figured out but I still don't know how can I attach the video file... this is my code now:
let response = await fetch(
'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&part=status&key=' + API_KEY,
{
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
snippet: {
title: "This is the title",
description: "This is the description.",
},
status: {
privacyStatus: 'private',
}
}),
}
);
I solved it! I took corse_upload.js from YouTube API Samples and implemented it in React Native / Expo.
Here is the API.js in React Native that I am using:
import React, { Component } from 'react';
export default class API extends Component {
constructor(props) {
super(props);
const obj = this;
const DRIVE_UPLOAD_URL = 'https://www.googleapis.com/upload/drive/v2/files/';
var options = props;
var noop = function() {};
this.file = options.file;
this.contentType = options.contentType || this.file.type || 'application/octet-stream';
this.metadata = options.metadata || {
'title': this.file.name,
'mimeType': this.contentType
};
this.token = options.token;
this.onComplete = options.onComplete || noop;
this.onProgress = options.onProgress || noop;
this.onError = options.onError || noop;
this.offset = options.offset || 0;
this.chunkSize = options.chunkSize || 0;
//this.retryHandler = new RetryHandler();
this.retryHandler = new obj.RetryHandler();
this.url = options.url;
if (!this.url) {
var params = options.params || {};
params.uploadType = 'resumable';
//this.url = this.buildUrl_(options.fileId, params, options.baseUrl);
this.url = obj.buildUrl_(options.fileId, params, options.baseUrl);
}
this.httpMethod = options.fileId ? 'PUT' : 'POST';
}
RetryHandler = function() {
this.interval = 1000; // Start at one second
this.maxInterval = 60 * 1000; // Don't wait longer than a minute
};
retry = function(fn) {
setTimeout(fn, this.interval);
this.interval = this.nextInterval_();
};
reset = function() {
this.interval = 1000;
};
nextInterval_ = function() {
var interval = this.interval * 2 + this.getRandomInt_(0, 1000);
return Math.min(interval, this.maxInterval);
};
getRandomInt_ = function(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
};
buildQuery_ = function(params) {
params = params || {};
return Object.keys(params).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
};
buildUrl_ = function(id, params, baseUrl) {
var url = baseUrl || DRIVE_UPLOAD_URL;
if (id) {
url += id;
}
var query = this.buildQuery_(params);
if (query) {
url += '?' + query;
}
return url;
};
upload = function() {
//var self = this;
var xhr = new XMLHttpRequest();
xhr.open(this.httpMethod, this.url, true);
xhr.setRequestHeader('Authorization', 'Bearer ' + this.token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('X-Upload-Content-Length', this.file.size);
xhr.setRequestHeader('X-Upload-Content-Type', this.contentType);
xhr.onload = function(e) {
if (e.target.status < 400) {
var location = e.target.getResponseHeader('Location');
this.url = location;
this.sendFile_();
} else {
this.onUploadError_(e);
}
}.bind(this);
xhr.onerror = this.onUploadError_.bind(this);
xhr.send(JSON.stringify(this.metadata));
};
sendFile_ = function() {
var content = this.file;
console.log(content);
var end = this.file.size;
if (this.offset || this.chunkSize) {
// Only bother to slice the file if we're either resuming or uploading in chunks
if (this.chunkSize) {
end = Math.min(this.offset + this.chunkSize, this.file.size);
}
content = content.slice(this.offset, end);
console.log(content);
}
var xhr = new XMLHttpRequest();
xhr.open('PUT', this.url, true);
xhr.setRequestHeader('Content-Type', this.contentType);
xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size);
xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
if (xhr.upload) {
xhr.upload.addEventListener('progress', this.onProgress);
}
xhr.onload = this.onContentUploadSuccess_.bind(this);
xhr.onerror = this.onContentUploadError_.bind(this);
xhr.send(content);
console.log(content);
};
resume_ = function() {
var xhr = new XMLHttpRequest();
xhr.open('PUT', this.url, true);
xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size);
xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
if (xhr.upload) {
xhr.upload.addEventListener('progress', this.onProgress);
}
xhr.onload = this.onContentUploadSuccess_.bind(this);
xhr.onerror = this.onContentUploadError_.bind(this);
xhr.send();
};
extractRange_ = function(xhr) {
var range = xhr.getResponseHeader('Range');
if (range) {
this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1;
}
};
onContentUploadSuccess_ = function(e) {
if (e.target.status == 200 || e.target.status == 201) {
this.onComplete(e.target.response);
} else if (e.target.status == 308) {
this.extractRange_(e.target);
this.reset();
this.sendFile_();
}
};
onContentUploadError_ = function(e) {
if (e.target.status && e.target.status < 500) {
this.onError(e.target.response);
} else {
this.retry(this.resume_.bind(this));
}
};
onUploadError_ = function(e) {
this.onError(e.target.response); // TODO - Retries for initial upload
};
}
And here is the way I use it in my App.js:
import YTDAPI from './assets/API'
import RNFS from 'react-native-fs';
uploadMediaToYouTube = async function(accessToken, videoUri) {
const fileInfo = await RNFS.stat(videoUri);
var file = {
name: fileInfo.path.split('/').pop(),
size: fileInfo.size,
uri: fileInfo.path,
type: 'video/mp4'
}
var metadata = {
snippet: {
title: 'This is a new title',
description: 'This is a new description',
tags: ['youtube-cors-upload'],
categoryId: 22
},
status: {
privacyStatus: 'unlisted'
}
};
var uploader = new YTDAPI({
baseUrl: 'https://www.googleapis.com/upload/youtube/v3/videos',
file: file,
token: this.accessToken,
metadata: metadata,
params: {
part: Object.keys(metadata).join(',')
},
onError: function(data) {
console.log(data);
var message = data;
try {
var errorResponse = JSON.parse(data);
message = errorResponse.error.message;
} finally {
alert(message);
}
}.bind(this),
onProgress: function(data) {
var currentTime = Date.now();
var bytesUploaded = data.loaded;
var totalBytes = data.total;
var bytesPerSecond = bytesUploaded / ((currentTime - window.uploadStartTime) / 1000);
var estimatedSecondsRemaining = (totalBytes - bytesUploaded) / bytesPerSecond;
var percentageComplete = (bytesUploaded * 100) / totalBytes;
console.log("Uploaded: " + bytesUploaded + " | Total: " + totalBytes + " | Percentage: " + percentageComplete + " | Esitmated seconds remaining: " + estimatedSecondsRemaining);
}.bind(this),
onComplete: function(data) {
console.log("Complete");
}.bind(this)
});
window.uploadStartTime = Date.now();
uploader.upload();
}
For this to work you need to configure an API key, web ClientId and oauth2 consent screen on your Google Cloud Console and allow Youtube Data V3 API library and authenticate an user to get an access_token that you will pass to my function.
UPDATE
Getting the file with Fetch() causes App crash with large files because its tries to load the whole video file into memory. I fixed this issue by using react-native-fs. I updated my code above.
After trying with my own youtube account I am facing same problem.
The documentation of Youtube is not also clear. This question have simillar problem as yours, but this guy used axios.
He said he spent months on it, but failed to figure out. After that he used youtube-video-api from npm, https://www.npmjs.com/package/youtube-video-api.
I think you should move on to some other solution, don't stuck with this code.
Like you can host a node.js backend, and then send your video and video information to the backend to upload it to youtube for the app user.

Api call is not happening while calling Http Request using HttpClient in angular 7

I am converting a post API request written in javascript to typescript, but my new code seems to be not running as i do not see any network calls in the debugger. Please find my code snippets below.
javascript (working)
private resourcesAccessable(url, token, clientId, resources) {
var request = new XMLHttpRequest();
request.open('POST', url, false);
request.setRequestHeader("Authorization", "Bearer " + token);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
console.log(request);
var response ;
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
response = JSON.parse(request.responseText);
} else if (status == 403) {
console.log('Authorization request was denied by the server.');
return null;
} else {
console.log('Could not obtain authorization data from server.');
return null;
}
}
}
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&response_mode=permissions&audience="+clientId;
if(Array.isArray(resources)){
for (var i = 0; i < resources.length; i++) {
params = params+"&permission="+resources[i]
}
}
request.send(params);
console.log(response);
return response;
}
typescript (not working)
resourcesAccessable(url, token, clientId, resources) {
private http: HttpClient,
private payload
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + token
})
};
this.payload = new URLSearchParams();
this.payload.set('grant_type','urn:ietf:params:oauth:grant-type:uma-ticket');
this.payload.set('response_mode','permissions');
this.payload.set('audience', clientId);
this.payload.set('permission',resources);
return this.http.post(url, payload.toString(), httpOptions)
.pipe(
tap(
(data) => {
console.log('----->>>', data);
}
)
), error => {
console.log('error ' + JSON.stringify(error));
};
}
I have tried many things to run the above code but none of them worked for me.
Split your code into the following sections. Angular/RxJS is different from vanilla JavaScript. You create Observable http calls which the Subscriber then reads from.
Inject HttpClient into your class -- necessary for http calls to work. (Needs additional dependencies to work. Please refer https://angular.io/guide/http)
constructor(protected http: HttpClient) {}
Function Definition
resourcesAccessable(url, token, clientId, resources): Observable<any> {
const payload = new URLSearchParams()
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + token
})
}
payload.set('grant_type', 'urn:ietf:params:oauth:grant-type:uma-ticket')
payload.set('response_mode', 'permissions')
payload.set('audience', clientId)
payload.set('permission', resources)
return this.http.post(url, payload.toString(), httpOptions)
}
Function Call
this.resourcesAccessable('', '', '', '')
.subscribe(
(data) => {
console.log('----->>>', data);
}
, error => {
console.log('error ' + JSON.stringify(error));
},
() => console.log('Completed'));

Node.js can't set headers after they are sent

I'm working on a simple node.js project that requires authentication. I decided to use connect-redis for sessions and a redis-backed database to store user login data.
Here is what I have setup so far:
// Module Dependencies
var express = require('express');
var redis = require('redis');
var client = redis.createClient();
var RedisStore = require('connect-redis')(express);
var crypto = require('crypto');
var app = module.exports = express.createServer();
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({ secret: 'obqc487yusyfcbjgahkwfet73asdlkfyuga9r3a4', store: new RedisStore }));
app.use(require('stylus').middleware({ src: __dirname + '/public' }));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
// Message Helper
app.dynamicHelpers({
// Index Alerts
indexMessage: function(req){
var msg = req.sessionStore.indexMessage;
if (msg) return '<p class="message">' + msg + '</p>';
},
// Login Alerts
loginMessage: function(req){
var err = req.sessionStore.loginError;
var msg = req.sessionStore.loginSuccess;
delete req.sessionStore.loginError;
delete req.sessionStore.loginSuccess;
if (err) return '<p class="error">' + err + '</p>';
if (msg) return '<p class="success">' + msg + '</p>';
},
// Register Alerts
registerMessage: function(req){
var err = req.sessionStore.registerError;
var msg = req.sessionStore.registerSuccess;
delete req.sessionStore.registerError;
delete req.sessionStore.registerSuccess;
if (err) return '<p class="error">' + err + '</p>';
if (msg) return '<p class="success">' + msg + '</p>';
},
// Session Access
sessionStore: function(req, res){
return req.sessionStore;
}
});
// Salt Generator
function generateSalt(){
var text = "";
var possible= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!##$%^&*"
for(var i = 0; i < 40; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
// Generate Hash
function hash(msg, key){
return crypto.createHmac('sha256', key).update(msg).digest('hex');
}
// Authenticate
function authenticate(username, pass, fn){
client.get('username:' + username + ':uid', function(err, reply){
var uid = reply;
client.get('uid:' + uid + ':pass', function(err, reply){
var storedPass = reply;
client.get('uid:' + uid + ':salt', function(err, reply){
var storedSalt = reply;
if (uid == null){
return fn(new Error('cannot find user'));
}
if (storedPass == hash(pass, storedSalt)){
client.get('uid:' + uid + ':name', function(err, reply){
var name = reply;
client.get('uid:' + uid + ':username', function(err, reply){
var username = reply;
var user = {
name: name,
username: username
}
return fn(null, user);
});
});
}
});
});
});
fn(new Error('invalid password'));
}
function restrict(req, res, next){
if (req.sessionStore.user) {
next();
} else {
req.sessionStore.loginError = 'Access denied!';
res.redirect('/login');
}
}
function accessLogger(req, res, next) {
console.log('/restricted accessed by %s', req.sessionStore.user.username);
next();
}
// Routes
app.get('/', function(req, res){
res.render('index', {
title: 'TileTabs'
});
});
app.get('/restricted', restrict, accessLogger, function(req, res){
res.render('restricted', {
title: 'Restricted Section'
});
});
app.get('/logout', function(req, res){
console.log(req.sessionStore.user.username + ' has logged out.');
req.sessionStore.destroy(function(){
res.redirect('home');
});
});
app.get('/login', function(req, res){
res.render('login', {
title: 'TileTabs Login'
});
});
app.post('/login', function(req, res){
authenticate(req.body.username, req.body.password, function(err, user){
if (user) {
req.session.regenerate(function(){
req.sessionStore.user = user;
req.sessionStore.indexMessage = 'Authenticated as ' + req.sessionStore.user.name + '. Click to logout. ' + ' You may now access the restricted section.';
res.redirect('home');
console.log(req.sessionStore.user.username + ' logged in!');
});
} else {
req.sessionStore.loginError = 'Authentication failed, please check your '
+ ' username and password.';
res.redirect('back');
}
});
});
app.get('/register', function(req, res){
res.render('register', {
title: 'TileTabs Register'
});
});
app.post('/register', function(req, res){
var name = req.body.name;
var username = req.body.username;
var password = req.body.password;
var salt = generateSalt();
client.get('username:' + username + ':uid', function(err, reply){
if (reply !== null){
console.log(reply);
req.sessionStore.registerError = 'Registration failed, ' + username + ' already taken.';
res.redirect('back');
}
else{
client.incr('global:nextUserId');
client.get('global:nextUserId', function(err, reply){
client.set('username:' + username + ':uid', reply);
client.set('uid:' + reply + ':name', name);
client.set('uid:' + reply + ':username', username);
client.set('uid:' + reply + ':salt', salt);
client.set('uid:' + reply + ':pass', hash(password, salt));
});
req.sessionStore.loginSuccess = 'Thanks for registering! Try logging in!';
console.log(username + ' has registered!');
res.redirect('/login');
}
});
});
// Only listen on $ node app.js
if (!module.parent) {
app.listen(80);
console.log("Express server listening on port %d", app.address().port);
}
Registration works great. However, upon logging in with the correct user credentials, I am thrown the following error:
node.js:134
throw e; // process.nextTick error, or 'error' event on first tick
^
Error: Can't set headers after they are sent.
I have managed to identify the line that throws this error (res.redirect('home');) in app.post('/login'). Just wondering, besides my poorly written code, what I need to do to fix this error.
UPDATE:
Versions:
node 0.4.10
express 2.4.3
npm 1.0.22
redis 2.4.0 rc5
connect 1.6.0
connect-redis 1.0.6
Here is the link to my app:
http://dl.dropbox.com/u/4873115/TileTabs.zip
Update
The problem was authenticate(). Below I have I think correct implementation:
function authenticate(username, pass, fn){
client.get('username:' + username + ':uid', function (err, reply) {
var uid = reply;
client.get('uid:' + uid + ':pass', function(err, reply){
var storedPass = reply;
client.get('uid:' + uid + ':salt', function(err, reply){
var storedSalt = reply;
if (uid == null){
fn(new Error('cannot find user'));
return;
} else if (storedPass == hash(pass, storedSalt)) {
client.get('uid:' + uid + ':name', function(err, reply){
var name = reply;
client.get('uid:' + uid + ':username', function(err, reply){
var username = reply;
var user = {
name: name,
username: username
}
fn(null, user);
return;
});
});
} else {
return fn(new Error('invalid password'));
}
});
});
});
//return fn(new Error('invalid password'));
}
I can't run the example because I don't have your stylus files. Can't you archive your project and post it over here, so that we can also run your code. If my memory serves me right, you could have these problems when you combine old modules with new modules. Which versions of express, connect-redis, redis, connect, etc do you have installed??
P.S: I can not run your code immediately if you upload, because I have to go to bed and have to work in the morning. But hopefully somebody else can help you then. Or maybe it is matter of modules installed.
My guess is that req.sessionStore.destroy is probably sending a "Set-Cookie" header to expire/delete the session cookie and because there's IO involved, node has a chance to send the HTTP response header before your res.redirect code runs, and thus the error is generated. Try just doing your res.redirect directly inside app.post as opposed to inside the destroy callback and see if that avoids the error.
You may also be hitting this node.js bug if code is trying to read headers after they are sent.