I'm getting 'client' undefined from a library I am using for firebase rest transactions, and it is one I am pretty sure I used before with no problems. When I look in the library (the intellij websphere debugger takes me to a typescript file, must be map aware), I see this.client..., in a class method which I am calling, so this should definitely be defined.
So then I grabbed a different library, that happens to be all one file so I could put it directly in my src/ folder, (why fix a flat when there are other broken wheels lying around, right?) and .. same thing (only this time it is this.url because different library).
The basic structure does work, because: (compare commented code to inline Promise)
class FirebaseDb {
constructor() {}
async tx(command, path, payload, queryParams) {
const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]]
/*const unary = (command === 'get' || commmand === 'delete')
const operation = await (unary?
dbcmd(path, queryParams):
dbcmd(path, payload, queryParams))*/
const res = await new Promise((r,e)=>setTimeout(
()=>{
console.log('inner this: ' + this);
r({foo:'bar'})
}, 1000))
const operation = {ok: true, body: res}
if(!operation.ok)
throw new Error(`[FirebaseDb] ${FirebaseDb.fbcrest[command]} - error with TX. (status ${operation.status}: ${operation.statusText})`)
return operation.body
}
That promise works just fine. I hit my api url and get {"foo":"bar"} a second later. The console doesn't show an undefined this, it reports inner this: [object Object].
So here's the full two files (that snippet was from firebaseDb.js, which will not show in the below because it was just a demonstration):
firebaseDb.js
import restFirebase from './restful-firebase'
import config from 'config'
/* FirebaseDb is a specific Model Db Firebase object. It receives REST calls
and converts them to firebase REST calls, executes them on a firebase
connection, returning the result or erroring out if there is an error. this
could be swapped out with a similar one for MongoDB, etc */
class FirebaseDb {
constructor() {}
async tx(command, path, payload, queryParams) {
const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]]
const unary = (command === 'get' || commmand === 'delete')
const operation = await (unary?
dbcmd(path, queryParams):
dbcmd(path, payload, queryParams))
if(!operation.ok)
throw new Error(`[FirebaseDb] ${FirebaseDb.fbcrest[command]} - error with TX. (status ${operation.status}: ${operation.statusText})`)
return operation.body
}
}
FirebaseDb.fbcrest = {
get: 'get',
put: 'set',
patch: 'update',
post: 'push',
delete: 'remove'
}
FirebaseDb.db = restFirebase.factory(config.firebase.project)
FirebaseDb.ref = FirebaseDb.db({
paths: '/',
auth: config.firebase.auth
})
export default FirebaseDb
restful-firebase.js
(modified npm i rest-firebase, accepts path in tx calls, no use strict for debugging):
import request from 'request'
const TIMEOUT = 5000;
const baseRequest = request.defaults({timeout: TIMEOUT, json: true});
const VALID_ID = /^[-0-9a-zA-Z]{2,}$/;
const VALID_URL = /^https?:\/\/[\da-z\.-]+(\:\d+)?\/?$/;
const ERR_INVALID_ID = 'Invalid Firebase id.';
const ERR_NO_SECRET = 'A Firebase secret is required for this operation.';
class ResponseError extends Error {
constructor(opts, resp, body) {
super(resp.statusMessage);
this.name = 'ResponseError';
this.url = opts.url;
this.method = opts.method;
this.status = resp.statusCode;
this.authDebug = resp.headers['x-firebase-auth-debug'];
this.body = body;
}
}
class Request {
constructor(opts) {
this.rootPath = trimPath(opts.rootPath);
this.url = opts.url;
this.auth = opts.auth;
this.$logger = opts.logger || console;
}
toString() {
return Request.fixUrl(this.url);
}
static fixUrl(url) {
return url.endsWith('.json') ? url : `${url}.json`;
}
process(url, method, qs, payload) {
return new Promise((resolve, reject) => {
const opts = {
url: Request.fixUrl(url),
method: method,
qs: Object.assign({auth: this.auth}, qs)
};
if (payload !== undefined) {
opts.body = payload;
}
baseRequest(opts, (err, resp, body) => {
if (err) {
reject(err);
return;
}
const debugMessage = resp.headers['x-firebase-auth-debug'];
if (debugMessage) {
this.$logger.warn(debugMessage);
}
if (resp.statusCode >= 300) {
reject(new ResponseError(opts, resp, body));
return;
}
resolve(body);
});
});
}
rules(rules) {
if (!this.auth) {
return Promise.reject(new Error(ERR_NO_SECRET));
}
const opts = {
'method': 'GET',
'url': `${this.rootPath}/.settings/rules.json`,
'qs': {auth: this.auth}
};
return new Promise((resolve, reject) => {
if (rules) {
opts.method = 'PUT';
opts.body = rules;
opts.json = typeof(rules) === 'object';
}
request(opts, (err, resp, body) => {
if (err) {
reject(err);
return;
}
if (resp.statusCode >= 300) {
reject(new ResponseError(opts, resp, body));
return;
}
resolve(body);
});
});
}
get(path, qs) {
let url = this.url
if(path)
url += '/' + path
return this.process(url, 'GET', qs);
}
set(path, payload, qs) {
let url = this.url
if(path)
url += '/' + path
return this.process(url, 'PUT', qs, payload);
}
update(path, payload, qs) {
let url = this.url
if(path)
url += '/' + path
if (url.endsWith('/.json')) {
// no-op
} else if (url.endsWith('.json')) {
url = `${url.slice(0, -5)}/.json`;
} else if (url.endsWith('/')) {
url = `${url}.json`;
} else {
url = `${url}/.json`;
}
return this.process(url, 'PATCH', qs, payload);
}
push(path, patch, qs) {
let url = this.url
if(path)
url += '/' + path
return this.process(url, 'POST', qs, patch);
}
remove(path, qs) {
let url = this.url
if(path)
url += '/' + path
return this.process(url, 'DELETE', qs);
}
}
function trimPath(path) {
return path.replace(/\/+$/, '');
}
/**
* Create a firebase rest client factory.
*
* The clients will be bound to a firebase ID. You then can use relative path
* to create references to entities in your Firebase DB.
*
* Usage:
*
* const restFirebase = require('rest-firebase');
* const firebase = restFirebase.factory('some-id');
* const ref = firebase({paths: 'some/path', auth: 'some-oauth-token'});
*
* // you can pass parameters
* // (see https://www.firebase.com/docs/rest/api/#section-query-parameters)
* ref.get({shallow: true}).then(value => {
* // ...
* });
*
* #param {string} target Firebase ID or URL
* #return {function}
*
*/
function restFirebaseFactory(target) {
let rootPath;
if (VALID_URL.test(target)) {
rootPath = trimPath(target);
} else if (VALID_ID.test(target)) {
rootPath = `https://${target}.firebaseio.com`;
} else {
throw new Error(ERR_INVALID_ID);
}
function restFirebase(opts) {
const relPaths = opts && opts.paths || '';
const url = [rootPath].concat(relPaths).join('/');
return new Request(
Object.assign({}, opts, {rootPath, url})
);
}
return restFirebase;
}
exports.Request = Request;
exports.factory = restFirebaseFactory;
some of the "magic" that might be involved:
my .babelrc
{
"presets": ["latest"]
}
my gulpfile:
const gulp = require('gulp');
const babel = require('gulp-babel');
const clean = require('gulp-rimraf');
const dest = 'dist'
gulp.task('src', ['clean'], () => {
return gulp.src(['server.js', 'src/**/*.js'])
.pipe(babel({
plugins: ['syntax-async-functions','transform-async-to-generator', 'transform-runtime']
}))
.pipe(gulp.dest(dest));
});
gulp.task('node_modules', ['clean', 'src'], () => {
return gulp.src(['node_modules/vue/dist/vue.js', 'node_modules/vue-router/dist/vue-router.js', 'node_modules/vuex/dist/vuex.js'])
.pipe(gulp.dest(dest+'/node_modules'));
});
gulp.task('clean', () => {
gulp.src(dest+'/*', {read:false})
.pipe(clean())
})
gulp.task('default', ['node_modules', 'src'])
those do this to restful-firebase.js:
'use strict';
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var TIMEOUT = 5000;
var baseRequest = _request2.default.defaults({ timeout: TIMEOUT, json: true });
var VALID_ID = /^[-0-9a-zA-Z]{2,}$/;
var VALID_URL = /^https?:\/\/[\da-z\.-]+(\:\d+)?\/?$/;
var ERR_INVALID_ID = 'Invalid Firebase id.';
var ERR_NO_SECRET = 'A Firebase secret is required for this operation.';
var ResponseError = function (_Error) {
(0, _inherits3.default)(ResponseError, _Error);
function ResponseError(opts, resp, body) {
(0, _classCallCheck3.default)(this, ResponseError);
var _this = (0, _possibleConstructorReturn3.default)(this, (ResponseError.__proto__ || (0, _getPrototypeOf2.default)(ResponseError)).call(this, resp.statusMessage));
_this.name = 'ResponseError';
_this.url = opts.url;
_this.method = opts.method;
_this.status = resp.statusCode;
_this.authDebug = resp.headers['x-firebase-auth-debug'];
_this.body = body;
return _this;
}
return ResponseError;
}(Error);
var Request = function () {
function Request(opts) {
(0, _classCallCheck3.default)(this, Request);
this.rootPath = trimPath(opts.rootPath);
this.url = opts.url;
this.auth = opts.auth;
this.$logger = opts.logger || console;
}
(0, _createClass3.default)(Request, [{
key: 'toString',
value: function toString() {
return Request.fixUrl(this.url);
}
}, {
key: 'process',
value: function process(url, method, qs, payload) {
var _this2 = this;
return new _promise2.default(function (resolve, reject) {
var opts = {
url: Request.fixUrl(url),
method: method,
qs: (0, _assign2.default)({ auth: _this2.auth }, qs)
};
if (payload !== undefined) {
opts.body = payload;
}
baseRequest(opts, function (err, resp, body) {
if (err) {
reject(err);
return;
}
var debugMessage = resp.headers['x-firebase-auth-debug'];
if (debugMessage) {
_this2.$logger.warn(debugMessage);
}
if (resp.statusCode >= 300) {
reject(new ResponseError(opts, resp, body));
return;
}
resolve(body);
});
});
}
}, {
key: 'rules',
value: function rules(_rules) {
if (!this.auth) {
return _promise2.default.reject(new Error(ERR_NO_SECRET));
}
var opts = {
'method': 'GET',
'url': this.rootPath + '/.settings/rules.json',
'qs': { auth: this.auth }
};
return new _promise2.default(function (resolve, reject) {
if (_rules) {
opts.method = 'PUT';
opts.body = _rules;
opts.json = (typeof _rules === 'undefined' ? 'undefined' : (0, _typeof3.default)(_rules)) === 'object';
}
(0, _request2.default)(opts, function (err, resp, body) {
if (err) {
reject(err);
return;
}
if (resp.statusCode >= 300) {
reject(new ResponseError(opts, resp, body));
return;
}
resolve(body);
});
});
}
}, {
key: 'get',
value: function get(path, qs) {
var url = this.url;
if (path) url += '/' + path;
return this.process(url, 'GET', qs);
}
}, {
key: 'set',
value: function set(path, payload, qs) {
var url = this.url;
if (path) url += '/' + path;
return this.process(url, 'PUT', qs, payload);
}
}, {
key: 'update',
value: function update(path, payload, qs) {
var url = this.url;
if (path) url += '/' + path;
if (url.endsWith('/.json')) {
// no-op
} else if (url.endsWith('.json')) {
url = url.slice(0, -5) + '/.json';
} else if (url.endsWith('/')) {
url = url + '.json';
} else {
url = url + '/.json';
}
return this.process(url, 'PATCH', qs, payload);
}
}, {
key: 'push',
value: function push(path, patch, qs) {
var url = this.url;
if (path) url += '/' + path;
return this.process(url, 'POST', qs, patch);
}
}, {
key: 'remove',
value: function remove(path, qs) {
var url = this.url;
if (path) url += '/' + path;
return this.process(url, 'DELETE', qs);
}
}], [{
key: 'fixUrl',
value: function fixUrl(url) {
return url.endsWith('.json') ? url : url + '.json';
}
}]);
return Request;
}();
function trimPath(path) {
return path.replace(/\/+$/, '');
}
/**
* Create a firebase rest client factory.
*
* The clients will be bound to a firebase ID. You then can use relative path
* to create references to entities in your Firebase DB.
*
* Usage:
*
* const restFirebase = require('rest-firebase');
* const firebase = restFirebase.factory('some-id');
* const ref = firebase({paths: 'some/path', auth: 'some-oauth-token'});
*
* // you can pass parameters
* // (see https://www.firebase.com/docs/rest/api/#section-query-parameters)
* ref.get({shallow: true}).then(value => {
* // ...
* });
*
* #param {string} target Firebase ID or URL
* #return {function}
*
*/
function restFirebaseFactory(target) {
var rootPath = void 0;
if (VALID_URL.test(target)) {
rootPath = trimPath(target);
} else if (VALID_ID.test(target)) {
rootPath = 'https://' + target + '.firebaseio.com';
} else {
throw new Error(ERR_INVALID_ID);
}
function restFirebase(opts) {
var relPaths = opts && opts.paths || '';
var url = [rootPath].concat(relPaths).join('/');
return new Request((0, _assign2.default)({}, opts, { rootPath: rootPath, url: url }));
}
return restFirebase;
}
exports.Request = Request;
exports.factory = restFirebaseFactory;
and specifically the section of code where this is undefined, throwing an error is the 3rd line in:
key: 'get',
value: function get(path, qs) {
var url = this.url;
if (path) url += '/' + path;
return this.process(url, 'GET', qs);
}
Usually it is safe to assume that the method is called as a callback in such case, this can be fixed by using arrow function or bind. Object methods cannot be detached from the object if they aren't bound.
Considering that
const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]]
is the place of interest, it should be
const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]].bind(FirebaseDb.ref)
or
const dbcmd = (...args) => FirebaseDb.ref[FirebaseDb.fbcrest[command]](...args)
Related
I'm a very beginner about GAS, and As you can see, my English is not perfect.
I want to upload json file on a github repository by GooleAppsScript.
I can update markdown file, but I cannot do json file.
There is no error on GAS, but when I check the commit log, it says '0 changed files'.
where part should I change the following code?
github repository
function editJson() {
var prop = PropertiesService.getScriptProperties().getProperties();
const date = new Date();
var option = { name: prop.NAME, email: prop.EMAIL };
var github = new GitHubAPI(prop.GITHUB_USERNAME, prop.GITHUB_REPO, prop.GITHUB_TOKEN, option);
var content = ['a', 'b', 'c']
var branch = github.getBranch(prop.GITHUB_BRANCH);
var pTree = github.getTree(branch['commit']['commit']['tree']['sha']);
var blob = github.createBlob(JSON.stringify(content));
var data = {
'tree': pTree['tree'].concat([{
'path': 'json/test.json',
'mode': '100644',
'type': 'blob',
'sha': blob['sha']
}])
};
var tree = github.createTree(data);
var commit = github.createCommit('commit!!', tree['sha'], branch['commit']['sha']);
var result = github.updateReference(prop.GITHUB_BRANCH, commit['sha']);
Logger.log(result);
}
(function(exports) {
var GitHubAPI;
GitHubAPI = (function(){
GitHubAPI.name = 'GitHubAPI';
function GitHubAPI(userid, repo, token, option) {
this.userid = userid;
this.repo = repo;
this.token = token;
this.option = option != null ? option : {};
if(!this.option.tz) this.option.tz = Session.getScriptTimeZone();
this.BASE_URL = 'https://api.github.com/repos/';
this.API_ENDPOINT = "" + this.BASE_URL + this.userid + '/' + this.repo;
}
GitHubAPI.prototype.runREST = function(method, endpoint, data) {
var params;
switch (method) {
case 'GET':
params = { headers : { Authorization: 'token ' + this.token } };
break;
case 'POST':
case 'PATCH':
params = {
headers: {
Authorization: 'token ' + this.token
},
method: method,
contentType: 'application/json',
payload: JSON.stringify(data)
};
break;
default:
throw 'undefined HTTP method: ' + method;
}
var response = UrlFetchApp.fetch(this.API_ENDPOINT + endpoint, params);
return JSON.parse(response);
};
GitHubAPI.prototype.get = function(endpoint){ return this.runREST('GET', endpoint, null); };
GitHubAPI.prototype.post = function(endpoint, data){ return this.runREST('POST', endpoint, data); };
GitHubAPI.prototype.patch = function(endpoint, data){ return this.runREST('PATCH', endpoint, data); };
GitHubAPI.prototype.toISOFormat = function(date, tz) {
return Utilities.formatDate(date, tz, "yyyy-MM-dd'T'HH:mm:ssXXX");
};
GitHubAPI.prototype.getBranch = function(branchName) {
return this.get('/branches/' + branchName);
};
GitHubAPI.prototype.createBlob = function(content) {
return this.post('/git/blobs', { 'content': content, 'encoding': 'utf-8' });
};
GitHubAPI.prototype.createCommit = function(message, treeSha, parentSha) {
var data = {
'message': message,
'author': {
'name': this.option.name,
'email': this.option.email,
'date': this.toISOFormat(new Date(), this.option.tz)
},
'parents': [parentSha],
'tree': treeSha
}
return this.post('/git/commits', data);
};
GitHubAPI.prototype.updateReference = function(branchName, commitSha) {
return this.patch('/git/refs/heads/' + branchName, { 'sha': commitSha });
};
GitHubAPI.prototype.getTree = function(treeSha) {
return this.get('/git/trees/' + treeSha);
};
GitHubAPI.prototype.createTree = function(data) {
return this.post('/git/trees', data);
};
return GitHubAPI;
})();
return exports.GitHubAPI = GitHubAPI;
})(this);
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)
}
};
I have a modified code in react-native for fetching data with server, that works fine. I want to add NetInfo to always check before fetching if telephone has connection to internet. Is it posible inside promise? How to connect this async function to my code?
'use strict';
var MAX_WAITING_TIME = 30000
var processStatus = function (response) {
// status "0" to handle local files fetching (e.g. Cordova/Phonegap etc.)
if (response.status === 200 || response.status === 0 || response.status === 201 || response.status === 422 || response.status === 302 ) {
return Promise.resolve(response)
} else if(response.status === 413) {
return Promise.reject(alert(____mobile.connection_error.large_file))
} else {
//return Promise.reject(alert("Process status: "+JSON.stringify(response )))
return Promise.reject(alert(____mobile.connection_error.top));
console.log("Process status: "+JSON.stringify(response ));
}
};
var parseJson = function (response) {
return response.json();
};
var getWrappedPromise = function () {
var wrappedPromise = {},
promise = new Promise(function (resolve, reject) {
wrappedPromise.resolve = resolve;
wrappedPromise.reject = reject;
});
wrappedPromise.then = promise.then.bind(promise);
wrappedPromise.catch = promise.catch.bind(promise);
wrappedPromise.promise = promise;// e.g. if you want to provide somewhere only promise, without .resolve/.reject/.catch methods
return wrappedPromise;
};
/* #returns {wrapped Promise} with .resolve/.reject/.catch methods */
var getWrappedFetch = function () {
var wrappedPromise = getWrappedPromise();
var args = Array.prototype.slice.call(arguments);// arguments to Array
fetch.apply(null, args)// calling original fetch() method
.then(function (response) {
wrappedPromise.resolve(response);
}, function (error) {
// wrappedPromise.reject(alert("Fetch status: " + error));
wrappedPromise.reject(____mobile.connection_error.top);
console.log("Fetch status: " + error);
})
.catch(function (error) {
wrappedPromise.catch(error);
});
return wrappedPromise;
};
/**
* Fetch JSON by url
* #param { {
* url: {String},
* [cacheBusting]: {Boolean}
* } } params
* #returns {Promise}
*/
var postJSON = function (params) {
var headers1 = {}
if (params.json){
headers1 = {
'Accept': 'application/json',
'Content-Type': 'application/json'}
}
if (params.headersIn){
headers1 = params.headersIn
}
var methodTmp = 'POST'
if (params.methodIn) {
methodTmp = params.methodIn
}
console.log(methodTmp)
var wrappedFetch = getWrappedFetch(
params.cacheBusting ? params.url + '?' + new Date().getTime() : params.url,
{
method: methodTmp,//'POST',// optional, "GET" is default value
headers: headers1,
body: params.send_data
});
var timeoutId = setTimeout(function () {
wrappedFetch.reject(alert(____mobile.connection_error.timeout, ____mobile.connection_error.check_connection));// reject on timeout
}, MAX_WAITING_TIME);
return wrappedFetch.promise// getting clear promise from wrapped
.then(function (response) {
clearTimeout(timeoutId);
return response;
})
.then(processStatus)
.then(parseJson);
};
module.exports = postJSON;
What would be the bast way to implement: NetInfo.isConnected.fetch() so fetched would only worked when there is internet connection?
EDIT:
I want to use:
NetInfo.isConnected.fetch()
Yeah I have to rewrite this code, not to use getWrappedPromise and now I think is good time for it.
EDIT2: Ok I refactored this code fragment, hope its better. Any comments welcome. I tested and I'm not sure if I still need this NetInfo.isConnected.fetch(). Now there is no errors where there is no connection or am I missing something?
New code:
var processStatus = function (response) {
if (response == undefined) {
return null
}
// status "0" to handle local files fetching (e.g. Cordova/Phonegap etc.)
if (response.status === 200 || response.status === 0 || response.status === 201 || response.status === 422 || response.status === 302 ) {
return Promise.resolve(response)
} else if(response.status === 413) {
return Promise.reject(alert(____mobile.connection_error.large_file))
} else {
//return Promise.reject(alert("Process status: "+JSON.stringify(response )))
console.log("Process status: "+JSON.stringify(response ));
return Promise.reject(alert(____mobile.connection_error.top));
}
};
var parseJson = function (response) {
if (response == undefined) {
return null
}
return response.json();
};
var postJSON = function (params) {
var headers1 = {}
if (params.json){
headers1 = {
'Accept': 'application/json',
'Content-Type': 'application/json'}
}
if (params.headersIn){
headers1 = params.headersIn
}
var methodTmp = 'POST'
if (params.methodIn) {
methodTmp = params.methodIn
}
console.log(methodTmp)
var fetchPromise = fetch(params.cacheBusting ? params.url + '?' + new Date().getTime() : params.url,
{
method: methodTmp,//'POST',// optional, "GET" is default value
headers: headers1,
body: params.send_data
})// calling original fetch() method
.then(function (response) {
return response;
}, function (error) {
console.log("Fetch status: " + error);
return fetch
}).then(processStatus)
.then(parseJson);
// timeoutId = setTimeout(function () {
// wrappedFetch.reject(alert(____mobile.connection_error.timeout, ____mobile.connection_error.check_connection));// reject on timeout
// }, MAX_WAITING_TIME);
return fetchPromise
};
This is my 3rd party node-js module:
var knox = require('knox')
, Resource = require('deployd/lib/resource')
, httpUtil = require('deployd/lib/util/http')
, formidable = require('formidable')
, fs = require('fs')
, util = require('util')
, path = require('path');
function S3Bucket(name, options) {
Resource.apply(this, arguments);
if (this.config.key && this.config.secret && this.config.bucket) {
this.client = knox.createClient({
key: this.config.key
, secret: this.config.secret
, bucket: this.config.bucket
});
}
}
util.inherits(S3Bucket, Resource);
module.exports = S3Bucket;
S3Bucket.label = "S3 Bucket";
S3Bucket.prototype.clientGeneration = true;
S3Bucket.events = ["upload", "get", "delete"];
S3Bucket.basicDashboard = {
settings: [{
name: 'bucket'
, type: 'string'
}, {
name: 'key'
, type: 'string'
}, {
name: 'secret'
, type: 'string'
}]
};
S3Bucket.prototype.handle = function (ctx, next) {
var req = ctx.req
, bucket = this
, domain = {url: ctx.url};
if (!this.client) return ctx.done("Missing S3 configuration!");
if (req.method === "POST" && !req.internal && req.headers['content-type'].indexOf('multipart/form-data') === 0) {
var form = new formidable.IncomingForm();
var remaining = 0;
var files = [];
var error;
var uploadedFile = function(err) {
if (err) {
error = err;
return ctx.done(err);
} else if (!err) {
remaining--;
if (remaining <= 0) {
if (req.headers.referer) {
httpUtil.redirect(ctx.res, req.headers.referer || '/');
} else {
ctx.done(null, files);
}
}
}
};
form.parse(req)
.on('file', function(name, file) {
remaining++;
if (bucket.events.upload) {
bucket.events.upload.run(ctx, {url: ctx.url, fileSize: file.size, fileName: file.filename}, function(err) {
if (err) return uploadedFile(err);
bucket.uploadFile(file.filename, file.size, file.mime, fs.createReadStream(file.path), uploadedFile);
});
} else {
bucket.uploadFile(file.filename, file.size, file.mime, fs.createReadStream(file.path), uploadedFile);
}
})
.on('error', function(err) {
ctx.done(err);
error = err;
});
req.resume();
return;
}
if (req.method === "POST" || req.method === "PUT") {
domain.fileSize = ctx.req.headers['content-length'];
domain.fileName = path.basename(ctx.url);
if (this.events.upload) {
this.events.upload.run(ctx, domain, function(err) {
if (err) return ctx.done(err);
bucket.upload(ctx, next);
});
} else {
this.upload(ctx, next);
}
} else if (req.method === "GET") {
if (ctx.res.internal) return next(); // This definitely has to be HTTP.
if (this.events.get) {
this.events.get.run(ctx, domain, function(err) {
if (err) return ctx.done(err);
bucket.get(ctx, next);
});
} else {
this.get(ctx, next);
}
} else if (req.method === "DELETE") {
if (this.events['delete']) {
this.events['delete'].run(ctx, domain, function(err) {
if (err) return ctx.done(err);
bucket.del(ctx, next);
});
} else {
this.del(ctx, next);
}
} else {
next();
}
};
S3Bucket.prototype.uploadFile = function(filename, filesize, mime, stream, fn) {
var bucket = this;
var headers = {
'Content-Length': filesize
, 'Content-Type': mime
};
this.client.putStream(stream, filename, headers, function(err, res) {
if (err) return ctx.done(err);
if (res.statusCode !== 200) {
bucket.readStream(res, function(err, message) {
fn(err || message);
});
} else {
fn();
}
});
};
S3Bucket.prototype.upload = function(ctx, next) {
var bucket = this
, req = ctx.req;
var headers = {
'Content-Length': req.headers['content-length']
, 'Content-Type': req.headers['content-type']
};
this.client.putStream(req, ctx.url, headers, function(err, res) {
if (err) return ctx.done(err);
if (res.statusCode !== 200) {
bucket.readStream(res, function(err, message) {
ctx.done(err || message);
});
} else {
ctx.done();
}
});
req.resume();
};
S3Bucket.prototype.get = function(ctx, next) {
var bucket = this;
var url = 'https://s3.amazonaws.com/' + this.config.bucket + ctx.url;
httpUtil.redirect(ctx.res, url);
};
S3Bucket.prototype.del = function(ctx, next) {
var bucket = this;
this.client.deleteFile(ctx.url, function(err, res) {
if (err) ctx.done(err);
if (res.statusCode !== 200) {
bucket.readStream(res, function(err, message) {
ctx.done(err || message);
});
} else {
ctx.done();
}
});
};
S3Bucket.prototype.readStream = function(stream, fn) {
var buffer = '';
stream.on('data', function(data) {
buffer += data;
}).on('end', function() {
fn(null, buffer);
}).on('error', function(err) {
fn(err);
});
};
inside s3-amazon-aws folder hence I do var s3bucket = require('s3-amazon-aws');
But, now if I have to call the handle function of the module, how do I do that? Also it requires 2 parameters such as ctx,next. How do I get those parameters?
Any help would be appreciated.
The module exports the S3Bucket constructor function. Use it to create a new object. You can then call the handle() method on this object (since it's part of the object's prototype).
var S3Bucket = require('s3-amazon-aws');
var bucket = new S3Bucket(name, options);
bucket.handle(ctx, next)
Regarding the various arguments you need to read the documentation of the library.
node.js code is known for turning into callback spaghetti.
What are the best techniques for overcoming this problem and writing clean, uncomplex, easy to understand callback code in node.js?
Take a look at Promises: http://promises-aplus.github.io/promises-spec/
It is an open standard which intended to solve this issue.
I am using node module 'q', which implements this standard: https://github.com/kriskowal/q
Simple use case:
var Q = require('q');
For example we have method like:
var foo = function(id) {
var qdef = Q.defer();
Model.find(id).success(function(result) {
qdef.resolve(result);
});
return (qdef.promise);
}
Then we can chain promises by method .then():
foo(<any-id>)
.then(function(result) {
// another promise
})
.then(function() {
// so on
});
It is also possible to creating promise from values like:
Q([]).then(function(val) { val.push('foo') });
And much more, see docs.
See also:
http://jeditoolkit.com/2012/04/26/code-logic-not-mechanics.html#post
http://wiki.commonjs.org/wiki/Promises/A
Several things can be done to avoid the 'matrioska-style'.
You can store callbacks to variables:
var on_read = function (foo, bar) {
// some logic
},
on_insert = function (err, data) {
someAsyncRead(data, on_read);
};
someAsyncInsert('foo', on_insert);
You can use some modules that help in those scenarios.
// Example using funk
var funk = require('funk');
for(var i = 0; i < 10; i++) {
asyncFunction(i, funk.add(function (data) {
this[i] = data;
}));
}
funk.parallel(function () {
console.log(this);
});
I'd suggest 1) using CoffeeScript and 2) using named callbacks and passing state between them in a hash, rather than either nesting callbacks or allowing argument lists to get very long. So instead of
var callback1 = function(foo) {
var callback2 = function(bar) {
var callback3 = function(baz) {
doLastThing(foo, bar, baz);
}
doSomethingElse(bar, callback3);
}
doSomething(foo, callback2);
}
someAsync(callback1);
you can instead simply write
callback1 = (state) -> doSomething state.foo, callback2
callback2 = (state) -> doSomethingElse state.bar, callback3
callback3 = (state) -> doLastThing state
someAsync callback1
once your doSomething, doSomethingElse and doLastThing have been rewritten to use/extend a hash. (You may need to write extra wrappers around external functions.)
As you can see, the code in this approach reads neatly and linearly. And because all callbacks are exposed, unit testing becomes much easier.
Try node-line
https://github.com/kevin0571/node-line
Usage:
var line = require("line");
line(function(next) {
obj.action1(param1, function(err, rs) {
next({
err: err,
rs: rs
});
});
}, function(next, data) {
if (data.err) {
console.error(err);
return;
}
obj.action2(param2, function(err, rs) {
if (err) {
console.error(err);
return;
}
next(rs);
});
}, function(rs) {
obj.finish(rs);
});
For the most part, working Twitter OAuth2 application-only example, using Kris' Q promise library with https.request, Nodejs Express api route. First attempt user timeline GET. If 401 response, refreshing bearer-token then retry user timeline. I had to use Q.when to handle a promise that returns another promise (chaining) or a value.
/**
* Using Rails-like standard naming convention for endpoints.
* GET /things -> index
* POST /things -> create
* GET /things/:id -> show
* PUT /things/:id -> update
* DELETE /things/:id -> destroy
*/
'use strict';
// var _ = require('lodash');
var http = require('http');
var https = require('https');
var querystring = require('querystring');
var Q = require('q')
// Get list of twtimelines
exports.index = function(req, res) {
var tid = req.query.tid
if (tid) {
Q.when(reqTimeline(tid, true, res), function(value) {
// > value
// 404
// > body1
// '{"errors":[{"code":34,"message":"Sorry, that page does not exist."}]}'
})
} else {
res.json({
errors: [{
message: 'no tid specified in query'
}]
});
}
};
function reqPromise(options, postData) {
var deferred = Q.defer()
var req = https.request(options, function(res) {
// console.log("statusCode: ", res.statusCode);
// console.log("headers: ", res.headers);
var statusCode = res.statusCode
deferred.notify(res)
res.on('data', function(d) {
//process.stdout.write(d);
deferred.notify(d)
}).on('end', function() {
deferred.resolve(statusCode)
});
});
req.on('error', function(e) {
console.error(e);
deferred.reject(e)
});
req.write(postData);
req.end();
return deferred.promise
} // deferRequest
function isIncomingMessage(ot) {
return ot instanceof http.IncomingMessage
}
function isBuffer(ot) {
return ot instanceof Buffer
}
function reqTimeline(screen_name, reqBearerTokenOn401, res) {
var optionsUserTimeline = {
hostname: 'api.twitter.com',
path: '/1.1/statuses/user_timeline.json?' + querystring.stringify({
count: '3',
screen_name: screen_name
}),
method: 'GET',
headers: {
//'Authorization': 'Bearer ' + JSON.parse(body1).access_token
'Authorization': 'Bearer ' + process.env.BEARER_TOKEN
} // headers
};
console.log("optionsUserTimeline", optionsUserTimeline)
var statusCode;
var body1 = new Buffer(''); // default utf8 string buffer ?
return reqPromise(optionsUserTimeline, '')
.then(function(value) { // done
if (reqBearerTokenOn401 && value === 401) {
console.log("reqTimeline - requesting bearer token")
return reqBearerToken(screen_name, res)
}
console.log("reqTimeline - done done:", value)
res.end()
return value
},
function(reason) { // error
console.log("reqTimeline - error:", body1)
},
function(progress) {
console.log("reqTimeline - progress:", body1)
if (isIncomingMessage(progress)) {
body1 = body1.slice(0, 0) // re-set buffer
statusCode = progress.statusCode;
if (reqBearerTokenOn401 && statusCode === 401) {
// readyn for retry
} else {
res.writeHead(statusCode)
}
} else if (isBuffer(progress)) {
if (reqBearerTokenOn401 && statusCode === 401) {
body1 += progress
} else {
res.write(progress)
}
} else {
throw "reqTimeline - unexpected progress"
}
});
} // reqTimeline
function reqBearerToken(screen_name, res) {
var postData = querystring.stringify({
'grant_type': 'client_credentials'
})
var optionsBearerToken = {
hostname: 'api.twitter.com',
path: '/oauth2/token',
method: 'POST',
headers: {
'Authorization': 'Basic ' + new Buffer(
process.env.CONSUMER_KEY + ":" + process.env.CONSUMER_SECRET
).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'Content-Length': postData.length
} // headers
}
// console.log("key", process.env.CONSUMER_KEY)
// console.log("secret", process.env.CONSUMER_SECRET)
// console.log("buf", new Buffer(
// process.env.CONSUMER_KEY + ":" + process.env.CONSUMER_SECRET
// ).toString())
console.log("optionsBearerToken", optionsBearerToken)
var body2 = new Buffer(''); // default utf8 string buffer ?
return reqPromise(optionsBearerToken, postData)
.then(function(value) { // done
console.log("reqBearerToken - done:", body2)
if (value === 200) {
console.log("reqBearerToken - done done")
process.env.BEARER_TOKEN = JSON.parse(body2).access_token;
return reqTimeline(screen_name, false, res)
}
return value
}, function(reason) {
throw "reqBearerToken - " + reason
}, function(progress) {
if (isIncomingMessage(progress)) {
body2 = body2.slice(0, 0) // reset buffer
} else if (isBuffer) {
body2 += progress
} else {
throw "reqBearerToken - unexpected progress"
}
});
} // reqBearerToken