forked from IstoraMandiri/meteor-method-hooks
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmethod-hooks.js
More file actions
194 lines (173 loc) · 6.27 KB
/
method-hooks.js
File metadata and controls
194 lines (173 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
MethodHooks = {};
/**
* A hook to be run before or after a method.
* @name Hook
* @function
* @return {*} The result of the method. Ignored for before hooks, passed as the methodResult to subsequent method hooks.
* You can mutate the return value in after hooks.
* @param {{result: *, error: *, arguments: Array, hooksProcessed: Number}} An options parameter that has the result and
* error from calling the method and the arguments used to call that method. `result` and `error` are null for before
* hooks, since the method has not yet been called. On the client, after hooks are called when the method returns from
* the server, but before the callback is invoked. `hooksProcessed` gives you the number of hooks processed so far,
* since previous hooks may have mutated the arguments.
*
* After hooks can change the result values. Use `hooksProcessed` to keep track of how many modifications have been
* made.
*/
/**
* A collection of after hooks
* @type {Object.<String, [Hook]>} A mapping from method names to arrays of hooks
* @private
*/
MethodHooks._afterHooks = {};
/**
* A collection of before hooks
* @type {Object.<String, [Hook]>} A mapping from method names to arrays of hooks
* @private
*/
MethodHooks._beforeHooks = {};
/**
* The method handler definitions appropriate to the environment
*/
MethodHooks._handlers = Meteor.isClient ? Meteor.connection._methodHandlers : Meteor.server.method_handlers;
/**
* The original method handlers
* @type {Object.<String, Function>} Method handler mapping
* @private
*/
MethodHooks._originalMethodHandlers = {};
/**
* Wrappers
* @type {Object.<String, Function>} A mapping from method names to method functions
* @private
*/
MethodHooks._wrappers = {};
/**
* Initializes a new hook
* @param mapping {{}<String, [Hook]>} A place to store the mapping
* @param methodName {String} The name of the method
* @param hookFunction {function} The hook function
* @private
*/
MethodHooks._initializeHook = function (mapping, methodName, hookFunction) {
mapping[methodName] = mapping[methodName] || [];
mapping[methodName].push(hookFunction);
// Initialize a wrapper for the given method name. Idempotent, it will not erase existing handlers.
var method = MethodHooks._handlers[methodName];
// If no method is found, or a wrapper already exists, return
if (!method
|| MethodHooks._wrappers[methodName]) {
return;
}
// Get a reference to the original handler
MethodHooks._originalMethodHandlers[methodName] = method;
MethodHooks._wrappers[methodName] = function () {
// Get arguments you can mutate
var args = _.toArray(arguments);
// Call the before hooks
var beforeHooks = MethodHooks._beforeHooks[methodName];
_.each(beforeHooks, function (beforeHook, hooksProcessed) {
beforeHook.call(this, {
result: undefined,
error: undefined,
arguments: args,
hooksProcessed: hooksProcessed,
methodName: methodName
});
});
var methodResult;
var methodError;
// Call the main method body
try {
methodResult = MethodHooks._originalMethodHandlers[methodName].apply(this, args);
} catch (error) {
methodError = error;
}
// Call after hooks, providing the result and the original arguments
var afterHooks = MethodHooks._afterHooks[methodName];
_.each(afterHooks, function (afterHook, hooksProcessed) {
var hookResult = afterHook.call(this, {
result: methodResult,
error: methodError,
arguments: args,
hooksProcessed: hooksProcessed,
methodName: methodName
});
// If the after hook did not return a value and the methodResult is not undefined, warn and fix
if (_.isUndefined(hookResult)
&& !_.isUndefined(methodResult)) {
Meteor._debug('Expected the after hook to return a value.');
} else {
methodResult = hookResult;
}
});
// If an error was thrown, throw it after the after hooks. Ought to include the correct stack information
if (methodError) {
throw methodError;
}
// Return the method result, possibly modified by the after hook
return methodResult;
};
// Assign to a new handler
MethodHooks._handlers[methodName] = MethodHooks._wrappers[methodName];
};
/**
* Add a function to call before the specified method
* @param methodName {string}
* @param beforeFunction {Hook}
*/
MethodHooks.before = function (methodName, beforeFunction) {
MethodHooks._initializeHook(MethodHooks._beforeHooks, methodName, beforeFunction);
};
/**
* Add a function to call after the specified method
* @param methodName {string}
* @param afterFunction {Hook}
*/
MethodHooks.after = function (methodName, afterFunction) {
MethodHooks._initializeHook(MethodHooks._afterHooks, methodName, afterFunction);
};
/**
* Call the provided hook in values for the key'd method names
* @param dict {Object.<string, Hook>}
*/
MethodHooks.beforeMethods = function (dict) {
_.each(dict, function (v, k) {
MethodHooks.before(k, v);
});
};
/**
* Call the provided hook in values for the key'd method names
* @param dict {Object.<string, Hook>}
*/
MethodHooks.afterMethods = function (dict) {
_.each(dict, function (v, k) {
MethodHooks.after(k, v);
});
};
/**
* private function for a create dict for beforeAllMethods & afterAllMethods
* @param hookFunction {Hook}
*/
MethodHooks._createHookDict = function (hookFunction) {
var methodNames = Object.keys(MethodHooks._handlers);
var hooks = {};
_.each(methodNames, function (name) {
hooks[name] = hookFunction;
});
return hooks;
}
/**
* Add a function to call before all meteor methods
* @param hookFunction {Hook}
*/
MethodHooks.beforeAllMethods = function (hookFunction) {
MethodHooks.beforeMethods(MethodHooks._createHookDict(hookFunction));
}
/**
* Add a function to call after all meteor methods
* @param hookFunction {Hook}
*/
MethodHooks.afterAllMethods = function (hookFunction) {
MethodHooks.afterMethods(MethodHooks._createHookDict(hookFunction));
}