How is it possible to access the loopback context (or simple Express req object) from within the model's logic?
It is critical to be able to know more about the request itself (current user identity more than anything else) inside the model's logic. When I override a built-in method (via custom script or from the model.js file) or develop a custom remote method, I would like to access the Express req object.
As loopback.getCurrentContext() is declared to be buggy, I cannot use it.
Ideas?
PS:
I find this page confusing: http://loopback.io/doc/en/lb2/Using-current-context.html
First it's said (and marked in red as important!) it is not recommended to use LoopBackContext.getCurrentContext() and then it's used it in each example!?
What's the point to give examples that do not work? Should we simply ignore the complete page? If so, what about the context? :)
Any clarification on this topic is much appreciated.
You can get access to express req object by using remote hooks
var loopback = require('loopback');
module.exports = function (MyModel) {
MyModel.beforeRemote('findOne', function (ctx, model, next) {
//access to ctx.req
console.log(ctx.req.headers)
next()
})
MyModel.beforeRemote('my-custom-remote-method', function (ctx, model, next) {
console.log(ctx.req.headers)
next()
})
}
Sure, you can use a beforeRemote hook to modify the ctx.args property. This property is the input of the remote method (that is, custom or built-in). This way, you can copy a part of the request inside this property, and it will be passed to the build-in method
Example 1 with the built-in method findOne.
MyModel.beforeRemote('findOne', function (ctx, model, next) {
ctx.args.filter.extrafield = ctx.req.headers['some-header'];
next();
});
Then override the findOne method since it's what you want to do
MyModel.on('dataSourceAttached', function(obj){
var findOne = MyModel.findOne;
MyModel.findOne = function(filter, cb) {
console.log(filter.extrafield); // Print what was in the header
return findOne.apply(this, arguments);
};
});
And finally call the method with curl
curl -H "some-header: 'hello, world!'" localhost:3000/api/MyModel/findOne
Example 2 with a custom remote printToken, to help you understand further
MyModel.beforeRemote('printToken', function (ctx, model, next) {
ctx.args.token = ctx.req.headers['some-header'];
next();
});
MyModel.printToken = function(token, cb) {
console.log(token);
cb();
}
MyModel.remoteMethod(
'printToken',
{
accepts: {arg: 'token', type: 'string', optional: true}
}
);
Then call the remote with curl, and pass the expected header
curl -H "some-header: 'hello, world!'" localhost:3000/api/MyModel/printToken
EDIT: There is a simpler solution that only works for custom remote
When defining your remote method, it is possible to tell loopback to pass elements of the http request to your remote directly as an input argument
MyModel.remoteMethod(
'printToken',
{
accepts: [
{arg: 'req', type: 'object', 'http': {source: 'req'}},
{arg: 'res', type: 'object', 'http': {source: 'res'}}
]
}
);
This way, your remote can access the req and res objects. This is documented here
Related
I'm trying to define a function that sends a request in a collection Pre-Request scripts:
const doRequest = (callback) => {
const echoPostRequest = {
url: 'https://postman-echo.com/post',
method: 'POST',
header: 'headername1:value1',
body: {
mode: 'raw',
raw: JSON.stringify({ key: 'this is json' })
}
};
console.log('ready to send request');
pm.sendRequest(echoPostRequest, function (err, res) {
console.log('request sent', err ? err : res.json());
callback();
});
}
glbl = {
doRequest: doRequest
}
Then, in my main test (a simple GET to google), I have this in Pre-Request script:
glbl.doRequest(() => console.log('works!'));
However, the callback is never called, and the "request sent" log is never printed.
This is the output of my postman console:
ready to send request 11:58:02.257
GET http://www.google.com 11:58:02.262
Do you know what I'm doing wrong?
I can provide the exported collection as well if it helps.
Thanks!
Edit: it I move everything in the Pre-request scripts of the request (not the collection), everything works fine
There's a few things happening here.
Local scoping issues
Your glbl variable is missing a var, const or let keyword.
Missing this keyword does not cause the variable to bubble up and become global on it's own. The environment Pre-request Script and request Pre-request Script are of different scopes.
As you've experienced, when you move the script to solely the request level, everything is sitting in the same scope so this works fine.
Creating global functions
Postman has the facility to create your own global variables. This can be done programmatically through pm.globals.set and pm.globals.get.
https://www.getpostman.com/docs/v6/postman/environments_and_globals/variables#accessing-variables-through-scripts
There are some limitations to these variables: you can only store strings in them, so the object and function that you've created will not persist if we don't do something to change their type.
In this case before we set any variables we need to ensure we:
toString any functions
JSON.stringify any objects
Now the Pre-request script for our collection will look like:
const doRequest = (callback) => {
const echoPostRequest = {
url: 'https://postman-echo.com/post',
method: 'POST',
header: 'headername1:value1',
body: {
mode: 'raw',
raw: JSON.stringify({ key: 'this is json' })
}
};
console.log('ready to send request');
pm.sendRequest(echoPostRequest, function (err, res) {
console.log('request sent', err ? err : res.json());
callback();
});
};
const glbl = {
doRequest: doRequest.toString()
};
pm.globals.set('glbl', JSON.stringify(glbl));
In order to use this at a request level, we need to update our Pre-request Script there too:
const glbl = JSON.parse(pm.globals.get('glbl'));
const doRequest = eval(glbl.doRequest);
doRequest(() => console.log('works!'));
After several tests I found a very ugly hack to export JS objects from a pre-request script to an other.
Of curse you have to ensure that the script that expose values is called before the module that uses the exported stuff.
The hack came from the following considerations:
You can't export js values other than string in postman, but you can add properties in the modules exposed by nodejs, so I imported the "util" module of nodejs and injected stuff there.
const util = require("util");
util.myExport = {
myMethod(){
// ...
}
};
The pm object exposed is different in each script executed by postman (require("util").myExport.pm === pm returns false if require("util").myExport.pm was defined in another module), and only the current one can perform operation, so if I want to use pm in some of the exported method, I have to pass a reference of the current pm object. otherwise no method can be invoked.
const util = require("util");
util.myExport = {
myMethod(_pm){ // note that `_pm` is the reference passed here,
_pm.sendRequest(/*...*/) // otherwise this line wont work
}
};
If you define an asynchronous function (such as a wrapper for setTimeout), it wont open the new process (nor thread, as well as with Promise or async/await functions) unless you use the properties exposed in the caller module. So you can not perform asynchronous operation if you are not using the current module stuff.
const util = require("util");
util.myExport = {
myMethod(scope){
scope.setTimeout(() => { // async function from the caller module
scope.pm.sendRequest(/*...*/) // pm from the caller module
}, 9999)
}
};
Now you are ready to go! Just import the nodejs "util" module to have your functions in the current module, then you can call them as usual.
const util = require("util");
util.myExport.myMethod(this)
I'm trying to test my 'Container' component which handles a forms logic. It is using vue-router and the vuex store to dispatch actions to get a forms details.
I have the following unit code which isn't working as intended:
it('On route enter, it should dispatch an action to fetch form details', () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
wrapper.vm.$options.beforeRouteEnter[0]();
expect(getFormDetails.called).to.be.true;
});
With the following component (stripped of everything because I don't think its relevant (hopefully):
export default {
async beforeRouteEnter(to, from, next) {
await store.dispatch('getFormDetails');
next();
}
};
I get the following assertion error:
AssertionError: expected false to be true
I'm guessing it is because I am not mounting the router in my test along with a localVue. I tried following the steps but I couldn't seem to get it to invoke the beforeRouteEnter.
Ideally, I would love to inject the router with a starting path and have different tests on route changes. For my use case, I would like to inject different props/dispatch different actions based on the component based on the path of the router.
I'm very new to Vue, so apologies if I'm missing something super obvious and thank you in advance for any help! 🙇🏽
See this doc: https://lmiller1990.github.io/vue-testing-handbook/vue-router.html#component-guards
Based on the doc, your test should look like this:
it('On route enter, it should dispatch an action to fetch form details', async () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
const next = sinon.stub()
MyComponent.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next)
await wrapper.vm.$nextTick()
expect(getFormDetails.called).to.be.true;
expect(next.called).to.be.true
});
A common pattern with beforeRouteEnter is to call methods directly at the instantiated vm instance. The documentation states:
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
This is why simply creating a stub or mock callback of next does not work in this case. I solved the problem by using the following parameter for next:
// mount the component
const wrapper = mount(Component, {});
// call the navigation guard manually
Component.beforeRouteEnter.call(wrapper.vm, undefined, undefined, (c) => c(wrapper.vm));
// await
await wrapper.vm.$nextTick();
I want some props to be visible for GET method in loopback explorer, but I don't want to show them for POST method, for e.g. id property. How it can be done in loopback?
There are no built-in methods for this.
You need to do it in after remote for each remote method you want to be different from the default.
Model.afterRemote('GetMethod', function(ctx, instance, next){
var instance = ctx.result;
//reshape it
ctx.result = instance;
next();
});
UPDATE
If you want to affect this in explorer component so you need to create separate models with null datasource just for showing schema and use that in definition of remote method.
Model.remoteMethod('GetMethod', {
accepts: [
{
arg: 'req',
type: 'Object',
required: true,
http: {source: 'req'}
}
],
returns: {root: true, type: 'ModelDTOForSHow'},
http: {verb: 'get', status: 200, path: '/getter'}
});
And in ModelDTOForShow you hide some props and in another one some other props
I need to find a way to change the content type of a response I'm sending from a custom remote method. It seems that by default it's application/json.
I have a remote method that returns images, so it i need somehow to change the Content-Type.
Register remote hook and then set header on express-like res from context object. In the end call next function (if it's defined) to continue execution.
Model.afterRemote('fetch', function(ctx, instance, next) {
ctx.res.header('Content-Type', 'image/png');
next && next();
});
You could add the response object of express to your method signature
Model.remoteMethod(
'yourMethod',
{
accepts: [
{ arg: 'id', type: 'number', required: true },
{arg: 'res', type: 'object', 'http': {source: 'res'}}
],
http: {path: '/method/:id', verb: 'get'}
}
);
and then use this object to set the content-type
Model. yourMethod = function(id, res, cb) {
res.type("image/png");
cb();
}
Is there a way in Ember.js (and Ember-data) to send credentials to an api that requires Basic HTTP Authentication? I can see how it's done in JQuery here, but don't see any straightforward way to do it in Ember. I thought maybe adding something to the header would work (see below in coffeescript), but no success:
App.AuthAdapter = DS.RESTAdapter.extend(
host: 'https://my-api.example.com'
namespace: 'v1'
headers:
"Authorization Basic fooUsername:barPassword"
...
You can extend the default Rest adapter and add a headers hash which will be included in the ajax that's sent.
App.ApplicationAdapter = DS.RESTAdapter.extend(
headers:
withCredentials: true
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
)
Or you could take it a step farther and override the ajax method
App.ApplicationAdapter = DS.RESTAdapter.extend(
ajax: (url, type, hash) ->
adapter = this
new Ember.RSVP.Promise((resolve, reject) ->
hash = hash or {}
hash.url = url
hash.type = type
hash.dataType = "json"
hash.context = adapter
if hash.data and type isnt "GET"
hash.contentType = "application/json; charset=utf-8"
hash.data = JSON.stringify(hash.data)
if adapter.headers isnt `undefined`
headers = adapter.headers
hash.beforeSend = (xhr) ->
forEach.call Ember.keys(headers), (key) ->
xhr.setRequestHeader key, headers[key]
hash.success = (json) ->
Ember.run null, resolve, json
hash.error = (jqXHR, textStatus, errorThrown) ->
Ember.run null, reject, adapter.ajaxError(jqXHR)
Ember.$.ajax hash
)
)
Can you use $.ajaxPrefilter? e.g.
Ember.$.ajaxPrefilter (options) ->
options.xhrFields = { withCredentials: true }
options.username = 'fooUsername'
options.password = 'barPassword'
true # need to return non-falsy here
As #gerry3 stated $.ajaxPrefilter is a valid solution.
But if you want to solve a problem of dynamically changing your Headers AFTER an event, for instance, a successful LOGIN attempt, then you need to put more wires. In my case I need to send back a 'Token' Header that is provided by the server after a successful AJAX-login. But, of course, when the user initiates the App he's not logged-in already.
The problem is that once you reopen or extend the RESTAdapter, or define an ajaxPrefilter, even if you're binding it to a value (or localStorage as in my case) the class won't be following the current variable value. It's like a snapshot taken at some moment. So it's useless in my scenario.
I'm following Embercast Client Authentication which is a good start (code available), but instead of jQuery data-fetching I'm using Ember-Data.
So the trick is to observe the token and re-define the ajaxPrefilter as many times as you need it.
tokenChanged: function() {
this.get('token')=='' ?
localStorage.removeItem('token') :
localStorage.token = this.get('token');
$.ajaxPrefilter(function(options, originalOptions, xhr) {
return xhr.setRequestHeader('Token', localStorage.token);
});
}.observes('token')
Therefore, when the user logs-in he'll have a valid token and send it in every request to the server via the RESTAdapter.
Hope this helps someone.