UNPKG

9.14 kBJavaScriptView Raw
1import { createToken, createPlugin, memoize } from 'fusion-core';
2import { UniversalEventsToken } from 'fusion-plugin-universal-events';
3import bodyparser from 'koa-bodyparser';
4import mapObject from 'just-map-object';
5import isEqual from 'just-compare';
6
7/** Copyright (c) 2018 Uber Technologies, Inc.
8 *
9 * This source code is licensed under the MIT license found in the
10 * LICENSE file in the root directory of this source tree.
11 *
12 *
13 */
14const RPCToken = createToken('RPCToken');
15const RPCHandlersToken = createToken('RPCHandlersToken');
16const BodyParserOptionsToken = createToken('BodyParserOptionsToken');
17const RPCHandlersConfigToken = createToken('RPCHandlersConfigToken');
18
19const formatApiPath = apiPath => `/${apiPath}/`.replace(/\/{2,}/g, '/');
20
21/** Copyright (c) 2018 Uber Technologies, Inc.
22 *
23 * This source code is licensed under the MIT license found in the
24 * LICENSE file in the root directory of this source tree.
25 *
26 *
27 */
28
29/* eslint-env browser */
30
31/** Copyright (c) 2018 Uber Technologies, Inc.
32 *
33 * This source code is licensed under the MIT license found in the
34 * LICENSE file in the root directory of this source tree.
35 *
36 *
37 */
38class MissingHandlerError extends Error {
39 constructor(method) {
40 super(`Missing RPC handler for ${method}`);
41 this.code = 'ERR_MISSING_HANDLER';
42 Error.captureStackTrace(this, MissingHandlerError);
43 }
44
45}
46
47/** Copyright (c) 2018 Uber Technologies, Inc.
48 *
49 * This source code is licensed under the MIT license found in the
50 * LICENSE file in the root directory of this source tree.
51 *
52 *
53 */
54class ResponseError extends Error {
55 constructor(message) {
56 super(message);
57 this.code = null;
58 this.meta = null;
59 Error.captureStackTrace(this, ResponseError);
60 }
61
62}
63
64/** Copyright (c) 2018 Uber Technologies, Inc.
65 *
66 * This source code is licensed under the MIT license found in the
67 * LICENSE file in the root directory of this source tree.
68 *
69 *
70 */
71
72/* eslint-env node */
73const statKey$1 = 'rpc:method';
74/* Helper function */
75
76function hasHandler(handlers, method) {
77 return handlers.hasOwnProperty(method);
78}
79
80class RPC$1 {
81 constructor(emitter, handlers, ctx) {
82 if (!ctx || !ctx.headers) {
83 throw new Error('fusion-plugin-rpc requires `ctx`');
84 }
85
86 this.ctx = ctx;
87 this.emitter = emitter;
88 this.handlers = handlers;
89 return this;
90 }
91
92 async request(method, args) {
93 const startTime = ms();
94
95 if (!this.ctx) {
96 throw new Error('fusion-plugin-rpc requires `ctx`');
97 }
98
99 if (!this.emitter) {
100 throw new Error('fusion-plugin-rpc requires `emitter`');
101 }
102
103 const scopedEmitter = this.emitter.from(this.ctx);
104
105 if (!this.handlers) {
106 throw new Error('fusion-plugin-rpc requires `handlers`');
107 }
108
109 if (!hasHandler(this.handlers, method)) {
110 const e = new MissingHandlerError(method);
111
112 if (scopedEmitter) {
113 scopedEmitter.emit('rpc:error', {
114 method,
115 origin: 'server',
116 error: e
117 });
118 }
119
120 throw e;
121 }
122
123 try {
124 const result = await this.handlers[method](args, this.ctx);
125
126 if (scopedEmitter) {
127 scopedEmitter.emit(statKey$1, {
128 method,
129 status: 'success',
130 origin: 'server',
131 timing: ms() - startTime
132 });
133 }
134
135 return result;
136 } catch (e) {
137 if (scopedEmitter) {
138 scopedEmitter.emit(statKey$1, {
139 method,
140 error: e,
141 status: 'failure',
142 origin: 'server',
143 timing: ms() - startTime
144 });
145 }
146
147 throw e;
148 }
149 }
150
151}
152
153const pluginFactory$1 = () => createPlugin({
154 deps: {
155 emitter: UniversalEventsToken,
156 handlers: RPCHandlersToken,
157 bodyParserOptions: BodyParserOptionsToken.optional,
158 rpcConfig: RPCHandlersConfigToken.optional
159 },
160 provides: deps => {
161 const {
162 emitter,
163 handlers
164 } = deps;
165 const service = {
166 from: memoize(ctx => new RPC$1(emitter, handlers, ctx))
167 };
168 return service;
169 },
170 middleware: deps => {
171 const {
172 emitter,
173 handlers,
174 bodyParserOptions,
175 rpcConfig
176 } = deps;
177 if (!handlers) throw new Error('Missing handlers registered to RPCHandlersToken');
178 if (!emitter) throw new Error('Missing emitter registered to UniversalEventsToken');
179 const parseBody = bodyparser(bodyParserOptions);
180 const apiPath = formatApiPath(rpcConfig && rpcConfig.apiPath ? rpcConfig.apiPath : 'api');
181 return async (ctx, next) => {
182 await next();
183 const scopedEmitter = emitter.from(ctx);
184
185 if (ctx.method === 'POST' && ctx.path.startsWith(apiPath)) {
186 const startTime = ms(); // eslint-disable-next-line no-useless-escape
187
188 const pathMatch = new RegExp(`${apiPath}([^/]+)`, 'i');
189 const [, method] = ctx.path.match(pathMatch) || [];
190
191 if (hasHandler(handlers, method)) {
192 await parseBody(ctx, () => Promise.resolve());
193
194 try {
195 const result = await handlers[method](ctx.request.body, ctx);
196 ctx.body = {
197 status: 'success',
198 data: result
199 };
200
201 if (scopedEmitter) {
202 scopedEmitter.emit(statKey$1, {
203 method,
204 status: 'success',
205 origin: 'browser',
206 timing: ms() - startTime
207 });
208 }
209 } catch (e) {
210 const error = e instanceof ResponseError ? e : new Error('UnknownError - Use ResponseError from fusion-plugin-rpc (or fusion-plugin-rpc-redux-react if you are using React) package for more detailed error messages');
211 ctx.body = {
212 status: 'failure',
213 data: {
214 message: error.message,
215 // $FlowFixMe
216 code: error.code,
217 // $FlowFixMe
218 meta: error.meta
219 }
220 };
221
222 if (scopedEmitter) {
223 scopedEmitter.emit(statKey$1, {
224 method,
225 error: e,
226 status: 'failure',
227 origin: 'browser',
228 timing: ms() - startTime
229 });
230 }
231 }
232 } else {
233 const e = new MissingHandlerError(method);
234 ctx.body = {
235 status: 'failure',
236 data: {
237 message: e.message,
238 code: e.code
239 }
240 };
241 ctx.status = 404;
242
243 if (scopedEmitter) {
244 scopedEmitter.emit('rpc:error', {
245 origin: 'browser',
246 method,
247 error: e
248 });
249 }
250 }
251 }
252 };
253 }
254});
255/* Helper functions */
256
257
258function ms() {
259 const [seconds, ns] = process.hrtime();
260 return Math.round(seconds * 1000 + ns / 1e6);
261}
262
263var serverDataFetching = true && pluginFactory$1();
264
265/** Copyright (c) 2018 Uber Technologies, Inc.
266 *
267 * This source code is licensed under the MIT license found in the
268 * LICENSE file in the root directory of this source tree.
269 *
270 *
271 */
272class RPC$2 {
273 constructor(handlers) {
274 this.handlers = handlers;
275 }
276
277 async request(method, args) {
278 if (!this.handlers) {
279 throw new Error('fusion-plugin-rpc requires `handlers`');
280 }
281
282 if (!this.handlers[method]) {
283 throw new MissingHandlerError(method);
284 }
285
286 return this.handlers[method](args);
287 }
288
289}
290
291const plugin = createPlugin({
292 deps: {
293 handlers: RPCHandlersToken,
294 emitter: UniversalEventsToken
295 },
296 provides: ({
297 handlers
298 } = {}) => {
299 return {
300 from: () => new RPC$2(handlers)
301 };
302 }
303});
304
305function _objectSpread$1(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty$1(target, key, source[key]); }); } return target; }
306
307function _defineProperty$1(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
308
309const getMockRpcHandlers = (fixtures, onMockRpc) => fixtures.reduce((rpcHandlers, fixture) => _objectSpread$1({}, rpcHandlers, mapObject(fixture, (rpcId, responseDetails) => async (...args) => {
310 const response = Array.isArray(responseDetails) ? responseDetails.filter(item => isEqual(item.args, args))[0].response : responseDetails;
311 onMockRpc && onMockRpc(rpcId, args, response);
312
313 if (response instanceof Error) {
314 throw response;
315 }
316
317 return response;
318})), {});
319
320/** Copyright (c) 2018 Uber Technologies, Inc.
321 *
322 * This source code is licensed under the MIT license found in the
323 * LICENSE file in the root directory of this source tree.
324 *
325 *
326 */
327var index = serverDataFetching;
328
329export default index;
330export { plugin as mock, ResponseError, BodyParserOptionsToken, RPCToken, RPCHandlersToken, RPCHandlersConfigToken, getMockRpcHandlers };