mastercontroller 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MasterAction.js +38 -1
- package/MasterActionFilters.js +10 -4
- package/MasterControl.js +67 -17
- package/MasterCors.js +34 -8
- package/MasterError.js +74 -13
- package/MasterRequest.js +62 -4
- package/MasterRouter.js +55 -43
- package/MasterSession.js +25 -11
- package/MasterSocket.js +121 -3
- package/package.json +6 -6
- package/MasterJWT.js +0 -112
package/MasterAction.js
CHANGED
|
@@ -133,6 +133,18 @@ class MasterAction{
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
returnReact(data, location){
|
|
137
|
+
|
|
138
|
+
var masterView = null;
|
|
139
|
+
data = data === undefined ? {} : data;
|
|
140
|
+
this.params = this.params === undefined ? {} : this.params;
|
|
141
|
+
this.params = tools.combineObjects(data, this.params);
|
|
142
|
+
var func = master.viewList;
|
|
143
|
+
this.params = tools.combineObjects(this.params, func);
|
|
144
|
+
var html = master.reactView.compile(this.__currentRoute.toController, this.__currentRoute.toAction, this.__currentRoute.root);
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
136
148
|
returnView(data, location){
|
|
137
149
|
|
|
138
150
|
var masterView = null;
|
|
@@ -165,7 +177,32 @@ class MasterAction{
|
|
|
165
177
|
response.end(end);
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
// Utility method to check if response is ready for writing
|
|
181
|
+
// Returns true if safe to continue, false if response already sent
|
|
182
|
+
waitUntilReady(){
|
|
183
|
+
// Check the primary response object first (matches existing returnJson pattern)
|
|
184
|
+
if (this.__response) {
|
|
185
|
+
return !this.__response._headerSent;
|
|
186
|
+
}
|
|
187
|
+
// Check request object response as fallback (matches existing redirectTo pattern)
|
|
188
|
+
if (this.__requestObject && this.__requestObject.response) {
|
|
189
|
+
return !this.__requestObject.response._headerSent;
|
|
190
|
+
}
|
|
191
|
+
// If neither exists, assume it's safe to continue (early in request lifecycle)
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Enhanced returnJson that checks readiness first
|
|
196
|
+
safeReturnJson(data){
|
|
197
|
+
if (this.waitUntilReady()) {
|
|
198
|
+
this.returnJson(data);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
console.warn('Attempted to send JSON response but headers already sent');
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
168
205
|
|
|
169
206
|
}
|
|
170
207
|
|
|
171
|
-
master.extendController(MasterAction);
|
|
208
|
+
master.extendController(MasterAction);
|
package/MasterActionFilters.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// version 1.
|
|
1
|
+
// version 1.7
|
|
2
2
|
var master = require('./MasterControl');
|
|
3
3
|
|
|
4
4
|
var _beforeActionFunc = {
|
|
@@ -54,7 +54,9 @@ class MasterActionFilters {
|
|
|
54
54
|
var flag = false;
|
|
55
55
|
if(_beforeActionFunc.namespace === obj.__namespace){
|
|
56
56
|
for (var a = 0; a < _beforeActionFunc.actionList.length; a++) {
|
|
57
|
-
|
|
57
|
+
var action = request.toAction.replace(/\s/g, '');
|
|
58
|
+
var incomingAction = _beforeActionFunc.actionList[a].replace(/\s/g, '');
|
|
59
|
+
if(incomingAction === action){
|
|
58
60
|
flag = true;
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -65,7 +67,9 @@ class MasterActionFilters {
|
|
|
65
67
|
__callBeforeAction(obj, request, emitter) {
|
|
66
68
|
if(_beforeActionFunc.namespace === obj.__namespace){
|
|
67
69
|
_beforeActionFunc.actionList.forEach(action => {
|
|
68
|
-
|
|
70
|
+
var action = action.replace(/\s/g, '');
|
|
71
|
+
var reqAction = request.toAction.replace(/\s/g, '');
|
|
72
|
+
if(action === reqAction){
|
|
69
73
|
emit = emitter;
|
|
70
74
|
// call function inside controller
|
|
71
75
|
_beforeActionFunc.callBack.call(_beforeActionFunc.that, request);
|
|
@@ -77,7 +81,9 @@ class MasterActionFilters {
|
|
|
77
81
|
__callAfterAction(obj, request) {
|
|
78
82
|
if(_afterActionFunc.namespace === obj.__namespace){
|
|
79
83
|
_afterActionFunc.actionList.forEach(action => {
|
|
80
|
-
|
|
84
|
+
var action = action.replace(/\s/g, '');
|
|
85
|
+
var reqAction = request.toAction.replace(/\s/g, '');
|
|
86
|
+
if(action === reqAction){
|
|
81
87
|
_afterActionFunc.callBack.call(_afterActionFunc.that, request);
|
|
82
88
|
}
|
|
83
89
|
});
|
package/MasterControl.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// MasterControl - by Alexander rich
|
|
2
|
-
// version 1.0.
|
|
2
|
+
// version 1.0.245
|
|
3
3
|
|
|
4
4
|
var url = require('url');
|
|
5
5
|
var fileserver = require('fs');
|
|
@@ -191,28 +191,71 @@ class MasterControl {
|
|
|
191
191
|
|
|
192
192
|
// sets up https or http server protocals
|
|
193
193
|
setupServer(type, credentials ){
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
if(type === "https"){
|
|
202
|
-
$that.serverProtocol = "https";
|
|
203
|
-
if(credentials){
|
|
204
|
-
return https.createServer(credentials, async function(req, res) {
|
|
194
|
+
try {
|
|
195
|
+
var $that = this;
|
|
196
|
+
if(type === "http"){
|
|
197
|
+
$that.serverProtocol = "http";
|
|
198
|
+
return http.createServer(async function(req, res) {
|
|
205
199
|
$that.serverRun(req, res);
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if(type === "https"){
|
|
203
|
+
$that.serverProtocol = "https";
|
|
204
|
+
if(credentials){
|
|
205
|
+
return https.createServer(credentials, async function(req, res) {
|
|
206
|
+
$that.serverRun(req, res);
|
|
207
|
+
});
|
|
208
|
+
}else{
|
|
209
|
+
throw "Credentials needed to setup https"
|
|
210
|
+
}
|
|
209
211
|
}
|
|
210
212
|
}
|
|
213
|
+
catch(error){
|
|
214
|
+
console.error("Failed to setup server:", error);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
211
217
|
}
|
|
212
218
|
|
|
213
219
|
async serverRun(req, res){
|
|
214
220
|
var $that = this;
|
|
215
221
|
console.log("path", `${req.method} ${req.url}`);
|
|
222
|
+
|
|
223
|
+
// Handle CORS preflight (OPTIONS) requests early and positively
|
|
224
|
+
if (req.method === 'OPTIONS') {
|
|
225
|
+
try {
|
|
226
|
+
if (this.cors && typeof this.cors.load === 'function') {
|
|
227
|
+
if (!this.cors.options) {
|
|
228
|
+
this.cors.init({
|
|
229
|
+
origin: true,
|
|
230
|
+
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
231
|
+
allowedHeaders: true,
|
|
232
|
+
credentials: false,
|
|
233
|
+
maxAge: 86400
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
this.cors.load({ request: req, response: res });
|
|
237
|
+
} else {
|
|
238
|
+
res.setHeader('access-control-allow-origin', '*');
|
|
239
|
+
res.setHeader('access-control-allow-methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
240
|
+
if (req.headers['access-control-request-headers']) {
|
|
241
|
+
res.setHeader('access-control-allow-headers', req.headers['access-control-request-headers']);
|
|
242
|
+
}
|
|
243
|
+
res.setHeader('access-control-max-age', '86400');
|
|
244
|
+
}
|
|
245
|
+
} catch (e) {
|
|
246
|
+
res.setHeader('access-control-allow-origin', '*');
|
|
247
|
+
res.setHeader('access-control-allow-methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
248
|
+
if (req.headers['access-control-request-headers']) {
|
|
249
|
+
res.setHeader('access-control-allow-headers', req.headers['access-control-request-headers']);
|
|
250
|
+
}
|
|
251
|
+
res.setHeader('access-control-max-age', '86400');
|
|
252
|
+
}
|
|
253
|
+
res.statusCode = 204;
|
|
254
|
+
res.setHeader('content-length', '0');
|
|
255
|
+
res.end();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
216
259
|
// parse URL
|
|
217
260
|
const parsedUrl = url.parse(req.url);
|
|
218
261
|
// extract URL path
|
|
@@ -221,6 +264,9 @@ class MasterControl {
|
|
|
221
264
|
// based on the URL path, extract the file extension. e.g. .js, .doc, ...
|
|
222
265
|
const ext = path.parse(pathname).ext;
|
|
223
266
|
|
|
267
|
+
// handle simple preflight configuration - might need a complex approch for all scenarios
|
|
268
|
+
|
|
269
|
+
|
|
224
270
|
// if extension exist then its a file.
|
|
225
271
|
if(ext === ""){
|
|
226
272
|
var requestObject = await this.middleware(req, res);
|
|
@@ -228,11 +274,14 @@ class MasterControl {
|
|
|
228
274
|
var loadedDone = false;
|
|
229
275
|
if (typeof $that._loadedFunc === 'function') {
|
|
230
276
|
loadedDone = $that._loadedFunc(requestObject);
|
|
231
|
-
|
|
277
|
+
if (loadedDone){
|
|
278
|
+
require(`${this.root}/config/load`)(requestObject);
|
|
279
|
+
}
|
|
232
280
|
}
|
|
233
|
-
|
|
281
|
+
else{
|
|
234
282
|
require(`${this.root}/config/load`)(requestObject);
|
|
235
283
|
}
|
|
284
|
+
|
|
236
285
|
|
|
237
286
|
}
|
|
238
287
|
}
|
|
@@ -315,6 +364,7 @@ class MasterControl {
|
|
|
315
364
|
return -1;
|
|
316
365
|
}
|
|
317
366
|
else{
|
|
367
|
+
|
|
318
368
|
var params = await this.request.getRequestParam(request, response);
|
|
319
369
|
return {
|
|
320
370
|
request : request,
|
package/MasterCors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// version
|
|
1
|
+
// version 0.0.3 - robust origin handling (all envs), creds-safe reflection, function origins, extended Vary
|
|
2
2
|
var master = require('./MasterControl');
|
|
3
3
|
|
|
4
4
|
// todo - res.setHeader('Access-Control-Request-Method', '*');
|
|
@@ -16,7 +16,9 @@ class MasterCors{
|
|
|
16
16
|
load(params){
|
|
17
17
|
if(params){
|
|
18
18
|
this.response = params.response;
|
|
19
|
-
this.request = params.
|
|
19
|
+
this.request = params.request;
|
|
20
|
+
// Always signal that response may vary by Origin and requested headers/method
|
|
21
|
+
try { this.response.setHeader('Vary', 'Origin, Access-Control-Request-Headers, Access-Control-Request-Method'); } catch(_) {}
|
|
20
22
|
this.configureOrigin();
|
|
21
23
|
this.configureMethods()
|
|
22
24
|
this.configureAllowedHeaders();
|
|
@@ -46,7 +48,13 @@ class MasterCors{
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
if(this.options.origin === true){
|
|
49
|
-
|
|
51
|
+
// If credentials are enabled, reflect request origin per spec
|
|
52
|
+
var requestOrigin = this.request.headers.origin;
|
|
53
|
+
if (this.options.credentials === true && requestOrigin) {
|
|
54
|
+
this.setHeader('access-control-allow-origin', requestOrigin);
|
|
55
|
+
} else {
|
|
56
|
+
this.setHeader('access-control-allow-origin', '*');
|
|
57
|
+
}
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
// remove all origins
|
|
@@ -54,11 +62,29 @@ class MasterCors{
|
|
|
54
62
|
this.removeHeader('access-control-allow-origin');
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
if(this.options.origin.constructor === Array){
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
if(this.options.origin.constructor === Array){
|
|
66
|
+
// Get the origin from the incoming request
|
|
67
|
+
var requestOrigin = this.request.headers.origin;
|
|
68
|
+
|
|
69
|
+
// Check if the request origin is in our allowed list
|
|
70
|
+
if(requestOrigin && this.options.origin.includes(requestOrigin)){
|
|
71
|
+
this.setHeader('access-control-allow-origin', requestOrigin);
|
|
60
72
|
}
|
|
61
|
-
|
|
73
|
+
// If no specific origin matches, don't set the header
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Function predicate support: (origin, req) => boolean|string
|
|
77
|
+
if (typeof this.options.origin === 'function'){
|
|
78
|
+
try {
|
|
79
|
+
var requestOrigin = this.request.headers.origin;
|
|
80
|
+
var res = this.options.origin(requestOrigin, this.request);
|
|
81
|
+
if (res === true && requestOrigin){
|
|
82
|
+
this.setHeader('access-control-allow-origin', requestOrigin);
|
|
83
|
+
}
|
|
84
|
+
else if (typeof res === 'string' && res){
|
|
85
|
+
this.setHeader('access-control-allow-origin', res);
|
|
86
|
+
}
|
|
87
|
+
} catch(_) {}
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
}
|
|
@@ -143,4 +169,4 @@ class MasterCors{
|
|
|
143
169
|
}
|
|
144
170
|
}
|
|
145
171
|
|
|
146
|
-
master.extend("cors", MasterCors);
|
|
172
|
+
master.extend("cors", MasterCors);
|
package/MasterError.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
// version 1.0.
|
|
2
|
+
// version 1.0.20 - improved console/error logging with syntax error code frames
|
|
3
3
|
var master = require('./MasterControl');
|
|
4
4
|
var winston = require('winston');
|
|
5
5
|
var fileserver = require('fs');
|
|
@@ -22,19 +22,23 @@ class MasterError{
|
|
|
22
22
|
]
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// will catch all promise exceptions
|
|
31
|
-
process.on('unhandledRejection', function (reason, promise) {
|
|
32
|
-
that.log(reason, "warn");
|
|
33
|
-
});
|
|
25
|
+
// Global error handlers with better diagnostics (stack and code frame)
|
|
26
|
+
process.on('uncaughtException', function (err) {
|
|
27
|
+
that._reportError(err, 'uncaughtException');
|
|
28
|
+
});
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
// will catch all promise exceptions
|
|
31
|
+
process.on('unhandledRejection', function (reason, promise) {
|
|
32
|
+
that._reportError(reason instanceof Error ? reason : new Error(String(reason)), 'unhandledRejection');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
process.on('rejectionHandled', function (reason, promise) {
|
|
36
|
+
that._reportError(reason instanceof Error ? reason : new Error(String(reason)), 'rejectionHandled');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
process.on('warning', function (warning) {
|
|
40
|
+
that._reportError(warning instanceof Error ? warning : new Error(String(warning)), 'warning');
|
|
41
|
+
});
|
|
38
42
|
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -111,6 +115,63 @@ class MasterError{
|
|
|
111
115
|
stack : msg.stack
|
|
112
116
|
});
|
|
113
117
|
}
|
|
118
|
+
|
|
119
|
+
// Enhanced error reporter: logs formatted stack and nearby source code
|
|
120
|
+
_reportError(err, tag){
|
|
121
|
+
try{
|
|
122
|
+
const name = err && err.name ? err.name : 'Error';
|
|
123
|
+
const message = err && err.message ? err.message : String(err);
|
|
124
|
+
const stack = err && err.stack ? String(err.stack) : '';
|
|
125
|
+
const header = `[${tag}] ${name}: ${message}`;
|
|
126
|
+
console.error('\u001b[31m' + header + '\u001b[0m');
|
|
127
|
+
if (stack) {
|
|
128
|
+
console.error(stack);
|
|
129
|
+
const loc = this._extractTopFrame(stack);
|
|
130
|
+
if (loc && loc.file && loc.line) {
|
|
131
|
+
const frame = this._buildCodeFrame(loc.file, loc.line, loc.column);
|
|
132
|
+
if (frame) {
|
|
133
|
+
console.error('\n\u001b[33mCode frame:\u001b[0m');
|
|
134
|
+
console.error(frame);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this.log(err, 'error');
|
|
139
|
+
} catch(e){
|
|
140
|
+
try { console.error('Error while reporting error:', e); } catch(_) {}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_extractTopFrame(stack){
|
|
145
|
+
try{
|
|
146
|
+
const lines = stack.split('\n');
|
|
147
|
+
for (let i = 0; i < lines.length; i++) {
|
|
148
|
+
const m = lines[i].match(/\((.*):(\d+):(\d+)\)/) || lines[i].match(/at ([^\s]+):(\d+):(\d+)/);
|
|
149
|
+
if (m) {
|
|
150
|
+
return { file: m[1], line: parseInt(m[2]), column: parseInt(m[3]||'0') };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
} catch(_) { return null; }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_buildCodeFrame(file, line, column){
|
|
158
|
+
try{
|
|
159
|
+
if (!fileserver.existsSync(file)) return null;
|
|
160
|
+
const src = fileserver.readFileSync(file, 'utf8').split(/\r?\n/);
|
|
161
|
+
const start = Math.max(1, line - 3);
|
|
162
|
+
const end = Math.min(src.length, line + 3);
|
|
163
|
+
const digits = String(end).length;
|
|
164
|
+
let out = '';
|
|
165
|
+
for (let i = start; i <= end; i++) {
|
|
166
|
+
const prefix = (i === line ? '>' : ' ') + String(i).padStart(digits, ' ') + ' | ';
|
|
167
|
+
out += prefix + src[i - 1] + '\n';
|
|
168
|
+
if (i === line && column && column > 0) {
|
|
169
|
+
out += ' '.repeat(prefix.length + column - 1) + '^\n';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
} catch(_) { return null; }
|
|
174
|
+
}
|
|
114
175
|
}
|
|
115
176
|
|
|
116
177
|
master.extend("error", MasterError);
|
package/MasterRequest.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
// version 0.0.
|
|
2
|
+
// version 0.0.2
|
|
3
3
|
|
|
4
4
|
var master = require('./MasterControl');
|
|
5
5
|
var url = require('url');
|
|
@@ -91,9 +91,17 @@ class MasterRequest{
|
|
|
91
91
|
resolve($that.parsedURL);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
break
|
|
95
|
+
case "text/plain" :
|
|
96
|
+
$that.fetchData(request, function(data){
|
|
97
|
+
$that.parsedURL.formData = data;
|
|
98
|
+
resolve($that.parsedURL);
|
|
99
|
+
|
|
100
|
+
});
|
|
101
|
+
|
|
94
102
|
break
|
|
95
103
|
default:
|
|
96
|
-
var errorMessage = `Cannot parse - We currently support text/html, application/json, multipart/form-data, and application/x-www-form-urlencoded - your sending us = ${contentType.type}`;
|
|
104
|
+
var errorMessage = `Cannot parse - We currently support text/plain, text/html, application/json, multipart/form-data, and application/x-www-form-urlencoded - your sending us = ${contentType.type}`;
|
|
97
105
|
resolve(errorMessage);
|
|
98
106
|
console.log(errorMessage);
|
|
99
107
|
}
|
|
@@ -110,6 +118,47 @@ class MasterRequest{
|
|
|
110
118
|
}
|
|
111
119
|
};
|
|
112
120
|
|
|
121
|
+
|
|
122
|
+
fetchData(request, func) {
|
|
123
|
+
|
|
124
|
+
let body = '';
|
|
125
|
+
let receivedBytes = 0;
|
|
126
|
+
const maxBytes = 1 * 1024 * 1024; // 1MB limit
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
|
|
131
|
+
request.on('data', (chunk) => {
|
|
132
|
+
receivedBytes += chunk.length;
|
|
133
|
+
|
|
134
|
+
// Prevent memory overload
|
|
135
|
+
if (receivedBytes > maxBytes) {
|
|
136
|
+
req.destroy(); // Close the connection
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Append chunk to body
|
|
141
|
+
body += chunk.toString('utf8');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
request.on('end', () => {
|
|
145
|
+
try {
|
|
146
|
+
// Process the plain text data here
|
|
147
|
+
const responseData = body;
|
|
148
|
+
func(responseData );
|
|
149
|
+
} catch (err) {
|
|
150
|
+
|
|
151
|
+
console.error('Processing error handling text/plain:', err);
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error("Failed to fetch data:", error);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
113
162
|
deleteFileBuffer(filePath){
|
|
114
163
|
fs.unlink(filePath, function (err) {
|
|
115
164
|
if (err) {
|
|
@@ -134,6 +183,10 @@ class MasterRequest{
|
|
|
134
183
|
});
|
|
135
184
|
|
|
136
185
|
}
|
|
186
|
+
|
|
187
|
+
stringToJson(request, func){
|
|
188
|
+
|
|
189
|
+
}
|
|
137
190
|
|
|
138
191
|
jsonStream(request, func){
|
|
139
192
|
//request.pipe(decoder);
|
|
@@ -143,8 +196,13 @@ class MasterRequest{
|
|
|
143
196
|
});
|
|
144
197
|
|
|
145
198
|
request.on('end', () => {
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
try {
|
|
200
|
+
var buff = JSON.parse(buffer);
|
|
201
|
+
func(buff);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
var buff = qs.parse(buffer);
|
|
204
|
+
func(buff);
|
|
205
|
+
}
|
|
148
206
|
});
|
|
149
207
|
|
|
150
208
|
}
|
package/MasterRouter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// version 0.0.
|
|
1
|
+
// version 0.0.247
|
|
2
2
|
|
|
3
3
|
var master = require('./MasterControl');
|
|
4
4
|
var toolClass = require('./MasterTools');
|
|
@@ -35,53 +35,65 @@ var tools = new toolClass();
|
|
|
35
35
|
var routeList = routeObject.routes;
|
|
36
36
|
var root = routeObject.root;
|
|
37
37
|
var isComponent = routeObject.isComponent;
|
|
38
|
+
try{
|
|
39
|
+
if(routeList.length > 0){
|
|
40
|
+
// loop through routes
|
|
41
|
+
for(var item in routeList){
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
requestObject.toController = routeList[item].toController;
|
|
44
|
+
requestObject.toAction = routeList[item].toAction;
|
|
45
|
+
var pathObj = normalizePaths(requestObject.pathName, routeList[item].path, requestObject.params);
|
|
46
|
+
// if we find the route that matches the request
|
|
47
|
+
if(pathObj.requestPath === pathObj.routePath && routeList[item].type === requestObject.type){
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
// call Constraint
|
|
50
|
+
if(typeof routeList[item].constraint === "function"){
|
|
51
|
+
|
|
52
|
+
var newObj = {};
|
|
53
|
+
//tools.combineObjects(newObj, master.controllerList);
|
|
54
|
+
newObj.next = function(){
|
|
55
|
+
currentRoute.root = root;
|
|
56
|
+
currentRoute.pathName = requestObject.pathName;
|
|
57
|
+
currentRoute.toAction = requestObject.toAction;
|
|
58
|
+
currentRoute.toController = requestObject.toController;
|
|
59
|
+
currentRoute.response = requestObject.response;
|
|
60
|
+
currentRoute.isComponent = isComponent;
|
|
61
|
+
emitter.emit("routeConstraintGood", requestObject);
|
|
62
|
+
};
|
|
63
|
+
routeList[item].constraint.call(newObj, requestObject);
|
|
64
|
+
return true;
|
|
65
|
+
}else{
|
|
48
66
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
currentRoute.isComponent = isComponent;
|
|
61
|
-
emitter.emit("routeConstraintGood", requestObject);
|
|
62
|
-
};
|
|
63
|
-
routeList[item].constraint.call(newObj, requestObject);
|
|
64
|
-
return true;
|
|
65
|
-
}else{
|
|
67
|
+
currentRoute.root = root;
|
|
68
|
+
currentRoute.pathName = requestObject.pathName;
|
|
69
|
+
currentRoute.toAction = requestObject.toAction;
|
|
70
|
+
currentRoute.toController = requestObject.toController;
|
|
71
|
+
currentRoute.response = requestObject.response;
|
|
72
|
+
currentRoute.isComponent = isComponent;
|
|
73
|
+
emitter.emit("routeConstraintGood", requestObject);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
if(pathObj.requestPath === pathObj.routePath && "options" ===requestObject.type.toLowerCase()){
|
|
80
|
+
// this means that the request is correct but its an options request means its the browser checking to see if the request is allowed
|
|
81
|
+
requestObject.response.writeHead(200, {'Content-Type': 'application/json'});
|
|
82
|
+
requestObject.response.end(JSON.stringify({"done": "true"}));
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
};
|
|
87
|
+
return -1;
|
|
77
88
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
else{
|
|
90
|
+
master.error.log(`route list is not an array`, "Error");
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch(e){
|
|
95
|
+
throw new Error("Error processing routes: " + e.stack);
|
|
96
|
+
}
|
|
85
97
|
};
|
|
86
98
|
|
|
87
99
|
var loadScopedListClasses = function(){
|
package/MasterSession.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
// version 0.0.
|
|
2
|
+
// version 0.0.22
|
|
3
3
|
|
|
4
4
|
var master = require('./MasterControl');
|
|
5
5
|
var cookie = require('cookie');
|
|
@@ -22,10 +22,17 @@ class MasterSession{
|
|
|
22
22
|
secret : this.createSessionID()
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
init(
|
|
25
|
+
init(options){
|
|
26
26
|
var $that = this;
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
|
|
28
|
+
// Combine the rest of the options carefully
|
|
29
|
+
this.options = {
|
|
30
|
+
...this.options,
|
|
31
|
+
...options
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if(this.options.TID){
|
|
35
|
+
this.options.secret = TID;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
return {
|
|
@@ -84,10 +91,17 @@ class MasterSession{
|
|
|
84
91
|
return this.secret;
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
setCookie(name, payload, response,
|
|
94
|
+
setCookie(name, payload, response, options){
|
|
88
95
|
var cookieOpt = options === undefined? this.options : options;
|
|
89
|
-
if(
|
|
90
|
-
|
|
96
|
+
if(typeof options === "object"){
|
|
97
|
+
if(options.secret){
|
|
98
|
+
response.setHeader('Set-Cookie', cookie.serialize(name, tools.encrypt(payload, cookieOpt.secret), cookieOpt));
|
|
99
|
+
}else{
|
|
100
|
+
|
|
101
|
+
response.setHeader('Set-Cookie', cookie.serialize(name, payload, cookieOpt));
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
}
|
|
92
106
|
else{
|
|
93
107
|
response.setHeader('Set-Cookie', cookie.serialize(name, payload, cookieOpt));
|
|
@@ -119,10 +133,10 @@ class MasterSession{
|
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
deleteCookie (name, response){
|
|
123
|
-
this.options
|
|
124
|
-
response.setHeader('Set-Cookie', cookie.serialize(name, "",
|
|
125
|
-
|
|
136
|
+
deleteCookie (name, response, options){
|
|
137
|
+
var cookieOpt = options === undefined? this.options : options;
|
|
138
|
+
response.setHeader('Set-Cookie', cookie.serialize(name, "", cookieOpt));
|
|
139
|
+
cookieOpt.expires = undefined;
|
|
126
140
|
}
|
|
127
141
|
|
|
128
142
|
// delete session and cookie
|
package/MasterSocket.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
// version 0.
|
|
1
|
+
// version 0.1.1
|
|
2
|
+
|
|
2
3
|
var master = require('./MasterControl');
|
|
4
|
+
const { Server } = require('socket.io');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
3
7
|
|
|
4
8
|
var jsUcfirst = function(string){
|
|
5
9
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
@@ -7,8 +11,82 @@ var jsUcfirst = function(string){
|
|
|
7
11
|
|
|
8
12
|
class MasterSocket{
|
|
9
13
|
|
|
10
|
-
init(){
|
|
14
|
+
init(serverOrIo, options = {}){
|
|
11
15
|
this._baseurl = master.root;
|
|
16
|
+
|
|
17
|
+
// Build Socket.IO options using master cors initializer when available
|
|
18
|
+
const defaults = this._buildDefaultIoOptions();
|
|
19
|
+
const ioOptions = mergeDeep(defaults, options || {});
|
|
20
|
+
|
|
21
|
+
// Determine whether we're given an io instance or an HTTP server
|
|
22
|
+
if (serverOrIo && typeof serverOrIo.of === 'function') {
|
|
23
|
+
// It's already an io instance
|
|
24
|
+
this.io = serverOrIo;
|
|
25
|
+
} else {
|
|
26
|
+
// Prefer explicit server, fallback to master.server
|
|
27
|
+
const httpServer = serverOrIo || master.server;
|
|
28
|
+
if (!httpServer) {
|
|
29
|
+
throw new Error('MasterSocket.init requires an HTTP server or a pre-created Socket.IO instance');
|
|
30
|
+
}
|
|
31
|
+
this.io = new Server(httpServer, ioOptions);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this._bind();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_buildDefaultIoOptions(){
|
|
38
|
+
const corsCfg = this._loadCorsConfig();
|
|
39
|
+
const transports = ['websocket', 'polling'];
|
|
40
|
+
const cors = {};
|
|
41
|
+
try {
|
|
42
|
+
if (corsCfg) {
|
|
43
|
+
if (typeof corsCfg.origin !== 'undefined') cors.origin = corsCfg.origin;
|
|
44
|
+
if (typeof corsCfg.credentials !== 'undefined') cors.credentials = !!corsCfg.credentials;
|
|
45
|
+
if (Array.isArray(corsCfg.methods)) cors.methods = corsCfg.methods;
|
|
46
|
+
if (Array.isArray(corsCfg.allowedHeaders)) cors.allowedHeaders = corsCfg.allowedHeaders;
|
|
47
|
+
} else {
|
|
48
|
+
// sensible defaults for dev
|
|
49
|
+
cors.origin = true;
|
|
50
|
+
cors.credentials = true;
|
|
51
|
+
cors.methods = ['GET','POST'];
|
|
52
|
+
}
|
|
53
|
+
} catch (_) {}
|
|
54
|
+
return { cors, transports };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_loadCorsConfig(){
|
|
58
|
+
try {
|
|
59
|
+
const cfgPath = path.join(master.root, 'config', 'initializers', 'cors.json');
|
|
60
|
+
if (fs.existsSync(cfgPath)) {
|
|
61
|
+
const raw = fs.readFileSync(cfgPath, 'utf8');
|
|
62
|
+
return JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
try { console.warn('[MasterSocket] Failed to load cors.json:', e && e.message ? e.message : e); } catch(_){}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_bind(){
|
|
71
|
+
const io = this.io;
|
|
72
|
+
io.on('connection', (socket) => {
|
|
73
|
+
try{
|
|
74
|
+
// Route all events through MasterSocket loader
|
|
75
|
+
socket.onAny((eventName, payload) => {
|
|
76
|
+
try{
|
|
77
|
+
// MasterSocket.load expects [action, payload]
|
|
78
|
+
const data = [eventName, payload];
|
|
79
|
+
if (master && master.socket && typeof master.socket.load === 'function') {
|
|
80
|
+
master.socket.load(data, socket, io);
|
|
81
|
+
}
|
|
82
|
+
}catch(e){
|
|
83
|
+
try { console.error('Socket routing error:', e?.message || e); } catch(_){}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}catch(e){
|
|
87
|
+
try { console.error('Socket connection handler error:', e?.message || e); } catch(_){}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
12
90
|
}
|
|
13
91
|
|
|
14
92
|
async load(data, socket, io){
|
|
@@ -45,4 +123,44 @@ class MasterSocket{
|
|
|
45
123
|
}
|
|
46
124
|
}
|
|
47
125
|
|
|
48
|
-
master.extend("socket", MasterSocket);
|
|
126
|
+
master.extend("socket", MasterSocket);
|
|
127
|
+
|
|
128
|
+
// shallow+deep merge helper
|
|
129
|
+
function isObject(item) {
|
|
130
|
+
return (item && typeof item === 'object' && !Array.isArray(item));
|
|
131
|
+
}
|
|
132
|
+
function mergeDeep(target, source) {
|
|
133
|
+
const output = Object.assign({}, target);
|
|
134
|
+
if (isObject(target) && isObject(source)) {
|
|
135
|
+
Object.keys(source).forEach(key => {
|
|
136
|
+
if (isObject(source[key])) {
|
|
137
|
+
if (!(key in target)) Object.assign(output, { [key]: source[key] });
|
|
138
|
+
else output[key] = mergeDeep(target[key], source[key]);
|
|
139
|
+
} else {
|
|
140
|
+
Object.assign(output, { [key]: source[key] });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return output;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
*
|
|
149
|
+
*
|
|
150
|
+
*
|
|
151
|
+
* It loads CORS and methods from config/initializers/cors.json automatically. During init, it reads master.root/config/initializers/cors.json and builds the Socket.IO options from:
|
|
152
|
+
origin, credentials, methods, allowedHeaders (if present)
|
|
153
|
+
transports defaults to ['websocket', 'polling']
|
|
154
|
+
If cors.json is missing or a field isn’t present, it falls back to:
|
|
155
|
+
cors: { origin: true, credentials: true, methods: ['GET','POST'] }
|
|
156
|
+
transports: ['websocket','polling']
|
|
157
|
+
You can still override anything explicitly:
|
|
158
|
+
master.socket.init(master.server, { cors: { origin: ['https://foo.com'], methods: ['GET','POST','PUT'] }, transports: ['websocket'] })
|
|
159
|
+
|
|
160
|
+
If you don’t pass a server/io, init() falls back to master.server:
|
|
161
|
+
master.socket.init() → uses master.server automatically
|
|
162
|
+
You can pass overrides as the second arg:
|
|
163
|
+
master.socket.init(undefined, { cors: { origin: ['https://app.com'] }, transports: ['websocket'] })
|
|
164
|
+
Or pass a prebuilt io:
|
|
165
|
+
const io = new Server(master.server, opts); master.socket.init(io)
|
|
166
|
+
*/
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"qs" : "^6.
|
|
4
|
-
"formidable": "^3.5.
|
|
5
|
-
"cookie": "^0.
|
|
6
|
-
"winston": "^3.
|
|
7
|
-
"glob" :"^
|
|
3
|
+
"qs" : "^6.14.0",
|
|
4
|
+
"formidable": "^3.5.4",
|
|
5
|
+
"cookie": "^1.0.2",
|
|
6
|
+
"winston": "^3.17.0",
|
|
7
|
+
"glob" :"^11.0.3"
|
|
8
8
|
},
|
|
9
9
|
"description": "A class library that makes using the Master Framework a breeze",
|
|
10
10
|
"homepage": "https://github.com/Tailor/MasterController#readme",
|
|
@@ -18,5 +18,5 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
20
|
},
|
|
21
|
-
"version": "1.2.
|
|
21
|
+
"version": "1.2.3"
|
|
22
22
|
}
|
package/MasterJWT.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
// version 0.0.17
|
|
3
|
-
|
|
4
|
-
var master = require('./MasterControl');
|
|
5
|
-
var crypto = require('crypto');
|
|
6
|
-
var toolClass = require('./MasterTools');
|
|
7
|
-
var tools = new toolClass();
|
|
8
|
-
|
|
9
|
-
//https://www.youtube.com/watch?v=67mezK3NzpU&t=2492s
|
|
10
|
-
class MasterJWT{
|
|
11
|
-
|
|
12
|
-
init(TID){
|
|
13
|
-
this.alg = "sha256";
|
|
14
|
-
var $that = this;
|
|
15
|
-
this.secret = this.createJWTID();
|
|
16
|
-
if(TID){
|
|
17
|
-
this.secret = TID;
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
sha256 : function(){
|
|
21
|
-
$that.alg = "sha256"
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
createJWTID(){
|
|
27
|
-
return crypto.randomBytes(20).toString('hex');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
getJWTID(){
|
|
31
|
-
return this.secret;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
create(payload, encrypted, encryptionKey){
|
|
35
|
-
var hmac = null;
|
|
36
|
-
var now = new Date();
|
|
37
|
-
var twoHoursLater = new Date(now.getTime() + (2*1000*60*60))
|
|
38
|
-
var encrypted = typeof encrypted === "undefined" ? false : true;
|
|
39
|
-
var encodePayload = null;
|
|
40
|
-
|
|
41
|
-
if(typeof payload === "string"){
|
|
42
|
-
throw "payload must be object not string";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
var header = {
|
|
46
|
-
typ: 'JWT',
|
|
47
|
-
alg: this.alg
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
var body = {
|
|
51
|
-
jti: tools.generateRandomKey('sha256'),
|
|
52
|
-
iat: Math.floor(new Date() / 1000),
|
|
53
|
-
exp: twoHoursLater
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
body = tools.combineObjects(body, payload);
|
|
57
|
-
|
|
58
|
-
if(encrypted === true){
|
|
59
|
-
header["encrypt"] = 'aes-256-ctr';
|
|
60
|
-
if(encryptionKey === undefined){
|
|
61
|
-
encodePayload = tools.base64().encode(tools.encrypt(JSON.stringify(body), this.secret));
|
|
62
|
-
}else{
|
|
63
|
-
encodePayload = tools.base64().encode(tools.encrypt(JSON.stringify(body), encryptionKey));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else{
|
|
67
|
-
encodePayload = tools.base64().encode(JSON.stringify(body));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if(encryptionKey === undefined){
|
|
72
|
-
hmac = crypto.createHmac(this.alg, this.secret);
|
|
73
|
-
}else{
|
|
74
|
-
hmac = crypto.createHmac(this.alg, encryptionKey);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
var encodeHeader = tools.base64().encode(JSON.stringify(header));
|
|
78
|
-
hmac.update(encodeHeader + "." + encodePayload);
|
|
79
|
-
var sig = hmac.digest('base64');
|
|
80
|
-
|
|
81
|
-
return encodeHeader + "." + encodePayload + "." + sig;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
verify(signature, encrypted, secret){
|
|
85
|
-
var secret = secret === undefined ? this.secret : secret;
|
|
86
|
-
var encrypted = typeof encrypted === "undefined" ? false : true;
|
|
87
|
-
var jwt = signature.split(".");
|
|
88
|
-
var decodeHeader = JSON.parse(tools.base64().decode(jwt[0]));
|
|
89
|
-
|
|
90
|
-
var hmac = crypto.createHmac(decodeHeader.alg, secret );
|
|
91
|
-
hmac.update(jwt[0] + "." + jwt[1]);
|
|
92
|
-
var ourSignature = hmac.digest('base64');
|
|
93
|
-
|
|
94
|
-
if(ourSignature === jwt[2]){
|
|
95
|
-
// if they are the same return json payload or un ecrypt payload
|
|
96
|
-
var decodePayload = tools.base64().decode(jwt[1]);
|
|
97
|
-
if(encrypted === true){
|
|
98
|
-
var decryptPayload = JSON.parse(tools.decrypt(decodePayload, secret ));
|
|
99
|
-
return decryptPayload;
|
|
100
|
-
}
|
|
101
|
-
else{
|
|
102
|
-
return decodePayload;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else{
|
|
106
|
-
return -1;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
master.extend("jwt", MasterJWT);
|