-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
288 lines (259 loc) · 9.51 KB
/
index.js
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
"use strict";
const bunyan = require("bunyan");
const corsMiddleware = require("restify-cors-middleware2");
const debug = require("debug")("transom:core");
const favicon = require("serve-favicon");
const path = require("path");
const restify = require("restify");
const restifyPlugins = require("restify").plugins;
const CookieParser = require("restify-cookies");
const semver = require("semver");
const PocketRegistry = require("pocket-registry");
const wrapper = require("./wrapper");
function createLogger(options) {
let bunyanLogger;
if (options.transom && options.transom.requestLogger) {
const requestLoggerOpts = options.transom.requestLogger || {};
requestLoggerOpts.name = requestLoggerOpts.name || "TransomJS";
// Use the provided logger, or create a default one.
bunyanLogger =
requestLoggerOpts.log || bunyan.createLogger(requestLoggerOpts);
}
return bunyanLogger;
}
// Process an array of Promises serially, discard the results.
function serial(promiseArray) {
return promiseArray.reduce((acc, current) => {
return acc.then(current);
}, Promise.resolve());
}
// Create a single registry and plugins array for TransomCore.
let _registry;
let _plugins;
function TransomCore() {
// Initialize on create
_registry = new PocketRegistry();
_plugins = [];
}
Object.defineProperties(TransomCore.prototype, {
registry: {
get: function () {
// Pre-initialize registry access.
return _registry;
},
},
});
TransomCore.prototype.configure = function (plugin, options) {
debug("Adding Transom plugin:", plugin.constructor.name);
options = options || {};
_plugins.push({
plugin,
options,
});
};
TransomCore.prototype.initialize = function (restifyServer, options) {
return new Promise((resolve, reject) => {
// Fail nicely on old versions of Node.
const minNodeVersion = "12.0.0";
if (semver.lte(process.version, minNodeVersion)) {
throw new Error(
`TransomJS doesn't support NodeJS versions older than ${minNodeVersion}, currently running ${process.version}.`
);
}
// Allow users to create their own Server & pass it in along with an options object.
if (!options) {
debug("Creating new Restify server");
options = restifyServer || {};
restifyServer = restify.createServer({
log: createLogger(options),
});
} else {
debug("Using the provided Restify server");
const tmpLogger = createLogger(options);
if (tmpLogger) {
restifyServer.log = tmpLogger;
}
}
options.transom = options.transom || {};
// Warn the developer if running with a non-zero timezone offset.
const suppressTimezoneWarning =
options.transom.suppressTimezoneWarning || false;
const offset = new Date().getTimezoneOffset();
if (!suppressTimezoneWarning && offset !== 0) {
const parts = process.argv[1].split(path.sep);
const entryFile = parts[parts.length - 1];
const line =
"*******************************************************************";
const warningMsg = `
This Node process is running with a timezone offset of ${offset} minutes.
It's recommended to run the service with an offset of 0 minutes using the
following line at the top of your ${entryFile} before any Dates are used.
process.env.TZ = 'Etc/GMT';\n`;
const yellow = "\x1b[33m%s\x1b[0m";
const reset = "\x1b[0m";
console.log(yellow, line + warningMsg + line, reset);
}
// Create a wrapper around Restify, exposing the most common methods
// and provide a 'restify' property for the ones we haven't exposed.
const server = wrapper.wrapServer(restifyServer, _registry);
// Apply the requestLogger, unless set to false!
if (options.transom && options.transom.requestLogger !== false) {
server.use(
restify.plugins.requestLogger({
log: server.log,
})
);
}
// Put the transom configuration and API definition into a global registry.
server.registry.set("transom-config", options);
// Make sure we use the same default URI prefix everywhere.
if (!server.registry.has("transom-config.definition.uri.prefix")) {
server.registry.set("transom-config.definition.uri.prefix", "/api/v1");
}
// Confirm that the URI prefix starts with a /, but doesn't end in one.
const prefix = server.registry.get("transom-config.definition.uri.prefix");
if (
!(
prefix.length > 0 &&
prefix[0] === "/" &&
prefix[prefix.length - 1] !== "/"
)
) {
throw new Error(`Invalid URI prefix: ${prefix}`);
}
debug("Using URI prefix:", prefix);
// Use CORS for handling cross-domain ajax requests.
const corsOptions = server.registry.get("transom-config.transom.cors", {});
if (corsOptions) {
debug("Adding CORS handling");
// Get an array of valid domain names for CORS and handle OPTIONS requests.
corsOptions.origins = corsOptions.origins || ["*"];
corsOptions.allowHeaders = (corsOptions.allowHeaders || []).concat([
"authorization",
]);
const cors = corsMiddleware(corsOptions);
server.pre(cors.preflight);
server.use(cors.actual);
}
// Parse body parameters into the req.params object.
const bodyOpts = server.registry.get(
"transom-config.transom.bodyParser",
{}
);
if (bodyOpts) {
debug("Adding Restify BodyParser plugin");
bodyOpts.mapParams =
bodyOpts.mapParams === undefined ? true : bodyOpts.mapParams; // default true
bodyOpts.limit = bodyOpts.limit === undefined ? 20000 : bodyOpts.limit; // default 20000
server.use(restifyPlugins.bodyParser(bodyOpts));
}
// Parse query parameters into the req.params object.
const queryOpts = server.registry.get(
"transom-config.transom.queryParser",
{}
);
if (queryOpts) {
debug("Adding Restify QueryParser plugin");
queryOpts.mapParams =
queryOpts.mapParams === undefined ? true : queryOpts.mapParams; // default true
server.use(restifyPlugins.queryParser(queryOpts));
}
// Parse url-encoded forms into the req.params object.
const encBodyOpts = server.registry.get(
"transom-config.transom.urlEncodedBodyParser",
{}
);
if (encBodyOpts) {
debug("Adding Restify UrlEncodedBodyParser plugin");
encBodyOpts.mapParams =
encBodyOpts.mapParams === undefined ? true : encBodyOpts.mapParams; // default true
server.use(restifyPlugins.urlEncodedBodyParser(encBodyOpts));
}
// Parse cookies into the req.cookies object.
const cookieParserOpts = server.registry.get(
"transom-config.transom.cookieParser",
{}
);
if (cookieParserOpts) {
debug("Adding Restify CookieParser plugin");
server.use(CookieParser.parse);
}
// Compress API responses with gzip.
const gzipOpts = server.registry.get(
"transom-config.transom.gzipResponse",
{}
);
if (gzipOpts) {
debug("Adding Restify GzipResponse plugin");
server.use(restifyPlugins.gzipResponse(gzipOpts));
}
// Use fullResponse, adding a bunch of Headers to the response.
const fullOpts = server.registry.get(
"transom-config.transom.fullResponse",
{}
);
if (fullOpts) {
debug("Adding Restify FullResponse plugin");
server.use(restifyPlugins.fullResponse(fullOpts));
}
// Provide a transom icon for API GET requests from a browser.
const faviconOpts = server.registry.get(
"transom-config.transom.favicon",
{}
);
if (faviconOpts) {
debug("Adding Favicon support");
faviconOpts.path =
faviconOpts.path || path.join(__dirname, "images", "favicon.ico");
server.use(favicon(faviconOpts.path));
}
// Create req.locals *before* initializing all our plugins!
server.use((req, res, next) => {
debug("Initializing req.locals and req.session");
req.locals = req.locals || {}; // Required by many of our API methods.
req.session = req.session || {}; // required by FacebookStrategy.
next();
});
// Configure each registered plugin, in the order they've been added.
const pluginInitPromises = [];
for (const each of _plugins) {
debug("Initializing Transom plugin:", each.plugin.constructor.name);
pluginInitPromises.push(() =>
each.plugin.initialize(server, each.options)
);
}
// All the initialize is done here
serial(pluginInitPromises)
.then(() => {
// run preStart each registered plugin, in the order they've been added.
const preStartPromises = [];
for (const each of _plugins) {
if (each.plugin.preStart) {
debug("Prestarting Transom plugin:", each.plugin.constructor.name);
preStartPromises.push(() =>
each.plugin.preStart(server, each.options)
);
}
}
return serial(preStartPromises);
})
.then(() => {
// Log all the routes to the debug output, if enabled.
if (debug.enabled && server.router && server.router.mounts) {
Object.keys(server.router.mounts).forEach((key) => {
const mount = server.router.mounts[key];
if (mount.spec) {
debug(`${mount.spec.method}\t${mount.spec.path}`);
}
});
}
debug("Transom plugins initialized");
resolve(server);
})
.catch((err) => {
console.error("transom:core Error initializing plugins ", err);
reject(err);
});
});
}; // end initialize
module.exports = TransomCore;