-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New hooks API (replaces monkey-patching for currency) #1683
Conversation
This is much more elegant than monkey-patching... but I'm not sure it solves the 1.0 problem. The core problem in 1.0 is that the This means that the I think the global API ( The first auction runs I haven't looked into the 1.0 branch closely enough yet... but I think there's also a race condition here. If a pub does something like:
then there's a good chance that |
So I attempted a few methods to solve the auction instance problems w/ 1.0 such as allowing callbacks to hooks and having addBidResponse be one, that way it can keep its closure context and we could have a global hook. However, the method that I think makes the most sense is fixing addBidResponse. In its current state it requires a lot of context to operate, and that context is grabbed from its outside scope creating a closure with a bunch of implicit dependencies. It's very hard to debug and follow. I suggest we make instance dependencies explicit through use of I updated this pull-request to be more accepting of context passing and binding for hook functions. The changes to auction.js would look similar to this: // outer module scope
export const addBidResponse = createHook('asyncSeries', function addBidResponse(adUnitCode, bid) {
...
function addBidToAuction() {
events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bid);
this.addBidReceived(bid); // instance functions and properties referenced by this
}
})
// callBids auction instance method could go back to the following
function callBids() {
...
adaptermanager.callBids(_adUnits, bidRequests, addBidResponse.bind(this), done.bind(this));
}; Basically we'll be treating |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will work, as a general strategy... but there are a few bugs which should probably be fixed before merging.
hook.fn.apply(this, args); | ||
}); | ||
}, | ||
asyncSeries: function(...args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure exactly what the issue(s) are... but I uncovered some weird bugs in asyncSeries
hooks.
The following test passes, as expected:
it('should allow context to be passed to hooks, but keep bound contexts', () => {
let context;
let fn = function() {
context = this;
};
let boundContext1 = {};
let calledBoundContext1;
let hook1 = function(next) {
calledBoundContext1 = this;
next()
}.bind(boundContext1);
let hookFn = createHook('asyncSeries', fn);
hookFn.addHook(hook1);
hookFn();
expect(calledBoundContext1).to.equal(boundContext1);
});
However, it begins failing if you add a second hook, like so:
it('should allow context to be passed to hooks, but keep bound contexts', () => {
let context;
let fn = function() {
context = this;
};
let boundContext1 = {};
let calledBoundContext1;
let hook1 = function(next) {
calledBoundContext1 = this;
next()
}.bind(boundContext1);
let boundContext2 = {};
let calledBoundContext2;
let hook2 = function(next) {
calledBoundContext2 = this;
next()
}.bind(boundContext2);
let hookFn = createHook('asyncSeries', fn);
hookFn.addHook(hook1);
// let newContext = {};
// hookFn = hookFn.bind(newContext);
hookFn();
// expect(context).to.equal(newContext);
expect(calledBoundContext1).to.equal(boundContext1);
expect(calledBoundContext2).to.equal(boundContext2);
});
It also fails with a single hook if the hookable function is bound:
it('should allow context to be passed to hooks, but keep bound contexts', () => {
let context;
let fn = function() {
context = this;
};
let boundContext1 = {};
let calledBoundContext1;
let hook1 = function(next) {
calledBoundContext1 = this;
next()
}.bind(boundContext1);
let hookFn = createHook('asyncSeries', fn);
hookFn.addHook(hook1);
let newContext = {};
hookFn = hookFn.bind(newContext);
hookFn();
expect(context).to.equal(newContext);
expect(calledBoundContext1).to.equal(boundContext1);
});
The error messages aren't very helpful... just TypeError: Cannot read property 'replace' of undefined
, with no line number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first test case you list I think is failing because you never added the second hook. I think adding hookFn.addHook(hook2);
should fix it.
The second test case is a bug. I wasn't properly maintaining the this
reference in the asyncSeriesNext
function. I updated the code to use a fat-arrow function to keep the proper this
and updated the test w/ your use case and it seems to be passing now. Thanks.
originalBidResponse = bidmanager.addBidResponse; | ||
bidmanager.addBidResponse = addBidResponseDecorator(bidmanager.addBidResponse); | ||
} | ||
bidmanager.addBidResponse.addHook(addBidResponseHook, 100); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any value in setting a priority when no hooks compete yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. If others add hooks later this one will still run first. I specifically wanted currency running first as it buffers responses (if the currency file isn't back yet).
updated |
* 'master' of https://github.com/prebid/Prebid.js: (22 commits) Update GetIntent adapter to 1.0 version (prebid#1721) Add `usePaymentRule` param to AN bidders (prebid#1778) New hooks API (replaces monkey-patching for currency) (prebid#1683) Change prebidServer to call client user syncs if they exist (prebid#1734) Fix Centro adapter to allow requests of the same units (prebid#1746) add vastUrl + media type for video bids Prebid Server (prebid#1739) Update adxcg adapter for prebid 1.0 (prebid#1741) Update yieldmoBid adapter request url (prebid#1771) Upgrade Quantcast adapter for Prebid 1.0 (prebid#1753) Fidelity Media Adapter update. Prebid v1.0 (prebid#1719) Kargo Adapter for Prebid 1.0 (prebid#1729) updated for prebid 1.0 api (prebid#1722) Add AdOcean adapter (prebid#1735) Update Conversant adapter to Prebid 1.0 (prebid#1711) Fix test-coverage bug (prebid#1765) Migrating TrustX adapter to 1.0 (prebid#1709) Update Improve Digital adapter for Prebid 1.0 (prebid#1728) Fixed the argument type on getUserSyncs. (prebid#1767) nanointeractive bid adapter (prebid#1627) Validating bid response params (prebid#1738) ...
* added hook module to prebid core that allows extension of arbitrary functions * remove unused dependency tiny-queue * change PluginFunction to HookedFunction * more hook documentation fixes * allow context for hooked functions * added tests for context * remove withContext, just use bind * fix in hooks so asyncSeries keeps proper bound context
* tag '0.32.0' of https://github.com/prebid/Prebid.js: (44 commits) Prebid 0.32.0 Release Commenting out tests that are failing in IE10 (prebid#1710) Update dfp.buildVideoUrl to accept adserver url (prebid#1663) Update rubicon adapter with new properties and 1.0 changes (prebid#1776) Added adUnitCode for compatibility (prebid#1781) Remove 'supported' from analytics adapter info (prebid#1780) Add TTL parameter to bid (prebid#1784) Update GetIntent adapter to 1.0 version (prebid#1721) Add `usePaymentRule` param to AN bidders (prebid#1778) New hooks API (replaces monkey-patching for currency) (prebid#1683) Change prebidServer to call client user syncs if they exist (prebid#1734) Fix Centro adapter to allow requests of the same units (prebid#1746) add vastUrl + media type for video bids Prebid Server (prebid#1739) Update adxcg adapter for prebid 1.0 (prebid#1741) Update yieldmoBid adapter request url (prebid#1771) Upgrade Quantcast adapter for Prebid 1.0 (prebid#1753) Fidelity Media Adapter update. Prebid v1.0 (prebid#1719) Kargo Adapter for Prebid 1.0 (prebid#1729) updated for prebid 1.0 api (prebid#1722) Add AdOcean adapter (prebid#1735) ...
* added hook module to prebid core that allows extension of arbitrary functions * remove unused dependency tiny-queue * change PluginFunction to HookedFunction * more hook documentation fixes * allow context for hooked functions * added tests for context * remove withContext, just use bind * fix in hooks so asyncSeries keeps proper bound context
Type of change
Description of change
This pull-request provides a new API for adding hooks into the Prebid core functionality. By wrapping code paths with
createHook
, Prebid.js modules (and possibly external code, if we decide to expose hooks) can extend the functionality of core code through various models provided inhook.js
. It follows in the spirit of https://github.com/webpack/tapable (the library webpack uses for adding extensibility). However, rather than adding extensibility for class methods through mixins (like tapable), I opted for adding extensibility to plain function since much of Prebid core is functional in nature.So far the only extensibility options for hooks that I added are
sync
andasyncSeries
, but other could be added if needed as seen within the tapable library's readme.This replaces the monkey-patching that currently takes place in the currency module. Once merged, the 1.0 branch can be updated to put the appropriate hook in place for currency to still function properly.
The hook API in this pull-request being used for
bidmanager.addBidResponse
in pre 1.0 is like the following:This allows any module that wants to extend
bidmanager.addBidResponse
functionality to import the method and add a hook.For making this compatible with 1.0 we need only to wrap
createHook
around whatever method we want extended (I think a newaddBidResponse
w/o the bidmanager) and expose the hook in a way that modules can get access to them . Either export the function in the new auctionManager (or wherever) or provide a global hook name as a third parameter tocreateHook
.Example:
With this new API we could hopefully start creating more hook-points in Prebid.js core which allows for more code to be moved into modules for optional inclusion.