| 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 | 1x
1x
1x
33x
33x
41x
41x
1x
3x
3x
3x
3x
41x
40x
7x
7x
40x
1x
39x
21x
39x
39x
39x
4x
39x
39x
67x
109x
109x
35x
74x
32x
32x
32x
74x
20x
20x
32x
12x
32x
32x
32x
20x
20x
20x
12x
12x
12x
12x
12x
1x
| const debug = require('debug')('socketcluster-api:Router');
const { serialize, deserialize } = require('./protobufCodec');
const NotFound = Symbol('404 NOT FOUND');
class Router {
constructor(pbRoot) {
this._routes = [];
this._pbRoot = pbRoot;
}
_verifyMethod(method) {
const allowedMethods = [ 'get', 'post', 'put', 'delete' ];
if (!allowedMethods.includes(method)) {
throw new Error(`Invalid method. Allowed methods are: ${allowedMethods.join(',')}`);
}
}
get(path, handler) {
return this.use('get', path, handler);
}
post(path, handler) {
return this.use('post', path, handler);
}
put(path, handler) {
return this.use('put', path, handler);
}
delete(path, handler) {
return this.use('delete', path, handler);
}
use(method, path, handler) {
this._verifyMethod(method);
if (typeof path === 'function') {
handler = path;
path = '/';
}
if (typeof handler !== 'function' && !(handler instanceof Router)) {
throw new Error('A handler (a function or a Router) must be passed to `use`.');
}
if (path[0] !== '/') {
path = '/' + path;
}
const parts = path.split('/').slice(1); // First element is empty (as path always start with '/').
let nextHandler = handler;
if (parts.length > 1) {
// This path contains multiple folders. Build `Router`s appropriately
nextHandler = new Router(this._pbRoot).use(method, parts.slice(1).join('/'), handler);
}
this._routes.push([ method, `/${parts[0]}`, nextHandler ]);
return this;
}
traverse(callback, absolutePath = '') {
this._routes.forEach(([ method, path, handler ]) => {
const fullPath = `${absolutePath}${path}`;
if (handler instanceof Router) {
handler.traverse(callback, fullPath);
} else {
callback(method, fullPath, handler);
}
});
}
_find(searchMethod, searchRoute) {
let resolved = false;
return new Promise((resolve, reject) => {
this.traverse((method, fullPath, handler) => {
if (!resolved && searchMethod === method && fullPath === searchRoute) {
resolved = true;
resolve(handler);
}
});
if (!resolved) {
reject(NotFound);
}
});
}
register(scSocket) {
scSocket.on('#api', this._handleEvent.bind(this));
}
_handleEvent(data, callback) {
let plain = {};
Iif (data.dataType) {
plain = deserialize(this._pbRoot.lookupType(data.dataType), data.buffer);
}
return this._find(data.method, data.resource)
.then(handler => handler(plain))
.then(({ dataType, responseData } = {}) => {
let buffer;
Iif (dataType) {
buffer = serialize(this._pbRoot.lookupType(dataType), responseData);
}
callback(null, { dataType, buffer, isError: false });
})
.catch(err => {
const dataType = '.socketclusterapi.ApiError';
Eif (err === NotFound) {
debug("No route for %o", data.resource);
const buffer = serialize(this._pbRoot.lookupType(dataType), {
code: 404,
reason: 'Not Found',
description: `Requested resource '${data.method} ${data.resource}' has no handler defined.`
});
callback(null, { dataType, buffer, isError: true });
} else if ( err.dataType && typeof err.datType === 'string' && err.data) {
const [ dataType, data ] = err;
const buffer = serialize(this._pbRoot.lookupType(dataType), data);
callback(null, { dataType: dataType, buffer, isError: true });
} else {
debug("Handler threw unexpectedly: %O", err);
const buffer = serialize(this._pbRoot.lookupType(dataType), {
code: 500,
reason: 'Internal Server Error',
description: `The resource threw an unexpected error.`
});
callback(null, { dataType, buffer, isError: true });
}
});
}
}
module.exports = Router;
|