godprotocol 2.2.94 → 2.2.95
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/Godprotocol.js +11 -0
- package/package.json +1 -1
- package/server/Cache.js +44 -0
- package/server/Resource.js +147 -0
- package/server/Routable/Header.js +19 -11
- package/server/Routable/Route_table.js +13 -0
- package/server/Routable/Services.js +46 -14
- package/server/serve_static_file.js +2 -0
- package/server/version_control.js +25 -13
package/Godprotocol.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Resource from "./server/Resource.js";
|
|
1
2
|
import Route_table from "./server/Routable/Route_table.js";
|
|
2
3
|
import version_middleware from "./server/version_control.js";
|
|
3
4
|
|
|
@@ -5,12 +6,22 @@ class GodProtocol {
|
|
|
5
6
|
constructor(config) {
|
|
6
7
|
this.db_config = config.db_config;
|
|
7
8
|
|
|
9
|
+
this.resource = new Resource(this);
|
|
10
|
+
|
|
8
11
|
this.static_path = config.static_path;
|
|
9
12
|
this.api_key = config.api_key;
|
|
10
13
|
this.platform_uri = this.uri_default_domain(config.platform_uri);
|
|
11
14
|
this.route_table = new Route_table(this);
|
|
15
|
+
|
|
16
|
+
this.memory = new Map();
|
|
17
|
+
|
|
18
|
+
this.on_start();
|
|
12
19
|
}
|
|
13
20
|
|
|
21
|
+
on_start = async (fn) => {
|
|
22
|
+
await fn(this);
|
|
23
|
+
};
|
|
24
|
+
|
|
14
25
|
uri_default_domain = (uri) => {
|
|
15
26
|
if (!uri.includes(".")) {
|
|
16
27
|
uri = `${uri}.savvyaisolution.com`;
|
package/package.json
CHANGED
package/server/Cache.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class Cache {
|
|
2
|
+
constructor(gp) {
|
|
3
|
+
this.gp = gp;
|
|
4
|
+
this.local = {};
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
set = async (key, value, opts = {}) => {
|
|
8
|
+
key = `${this.gp.platform_uri}:${key}`;
|
|
9
|
+
|
|
10
|
+
this.local[key] = value;
|
|
11
|
+
|
|
12
|
+
let Cache = await this.gp.route_table.get_service("cache");
|
|
13
|
+
if (!Cache) return;
|
|
14
|
+
|
|
15
|
+
await Cache.call("set", {
|
|
16
|
+
key,
|
|
17
|
+
value,
|
|
18
|
+
options: opts,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
get = async (key, fill) => {
|
|
23
|
+
key = `${this.gp.platform_uri}:${key}`;
|
|
24
|
+
|
|
25
|
+
let value = this.local[key];
|
|
26
|
+
if (value) return value;
|
|
27
|
+
|
|
28
|
+
let Cache = await this.gp.route_table.get_service("cache");
|
|
29
|
+
if (!Cache) return;
|
|
30
|
+
|
|
31
|
+
value = await Cache.call("get", {
|
|
32
|
+
key,
|
|
33
|
+
fill,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (value.ok) {
|
|
37
|
+
this.local[key] = value.data;
|
|
38
|
+
|
|
39
|
+
return value.data;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default Cache;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import Cache from "./Cache.js";
|
|
2
|
+
|
|
3
|
+
class Resource {
|
|
4
|
+
constructor(gp) {
|
|
5
|
+
this.gp = gp;
|
|
6
|
+
|
|
7
|
+
this.cache = new Cache(this.gp);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
hydrate_runtime = async () => {
|
|
11
|
+
this.runtime = await this.cache.get("runtime", {
|
|
12
|
+
active_requests: 0,
|
|
13
|
+
|
|
14
|
+
requests: new Map(),
|
|
15
|
+
|
|
16
|
+
handlers: {},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return this.runtime;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
persist_runtime = async () => {
|
|
23
|
+
await this.cache.set("runtime", this.runtime, { ttl: 86400 });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
incoming_request = async (req) => {
|
|
27
|
+
if (!(await this.hydrate_runtime())) return;
|
|
28
|
+
|
|
29
|
+
if (this.runtime.active_requests % 100 === 0) {
|
|
30
|
+
this.cleanup_handlers();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const route = `${req.method}:${req.url}`;
|
|
34
|
+
|
|
35
|
+
this.runtime.active_requests++;
|
|
36
|
+
|
|
37
|
+
this.runtime.requests.set(req.request_id, {
|
|
38
|
+
route,
|
|
39
|
+
started: performance.now(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!this.runtime.handlers[route]) {
|
|
43
|
+
this.runtime.handlers[route] = {
|
|
44
|
+
active: 0,
|
|
45
|
+
completed: 0,
|
|
46
|
+
|
|
47
|
+
average_duration: 0,
|
|
48
|
+
|
|
49
|
+
max_duration: 0,
|
|
50
|
+
min_duration: Infinity,
|
|
51
|
+
|
|
52
|
+
total_duration: 0,
|
|
53
|
+
|
|
54
|
+
last_seen: Date.now(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.runtime.handlers[route].active++;
|
|
59
|
+
|
|
60
|
+
await this.persist_runtime();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
compute_response = async (res) => {
|
|
64
|
+
if (!(await this.hydrate_runtime())) return;
|
|
65
|
+
|
|
66
|
+
const request = this.runtime.requests.get(res.request_id);
|
|
67
|
+
|
|
68
|
+
if (!request) return;
|
|
69
|
+
|
|
70
|
+
const handler = this.runtime.handlers[request.route];
|
|
71
|
+
|
|
72
|
+
if (!handler) {
|
|
73
|
+
this.runtime.requests.delete(res.request_id);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const duration = performance.now() - request.started;
|
|
78
|
+
|
|
79
|
+
handler.completed++;
|
|
80
|
+
|
|
81
|
+
// Exponential Moving Average
|
|
82
|
+
if (handler.average_duration === 0) {
|
|
83
|
+
handler.average_duration = duration;
|
|
84
|
+
} else {
|
|
85
|
+
handler.average_duration =
|
|
86
|
+
handler.average_duration * 0.9 + duration * 0.1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
handler.total_duration += duration;
|
|
90
|
+
|
|
91
|
+
handler.max_duration = Math.max(handler.max_duration, duration);
|
|
92
|
+
|
|
93
|
+
handler.min_duration = Math.min(handler.min_duration, duration);
|
|
94
|
+
|
|
95
|
+
handler.last_seen = Date.now();
|
|
96
|
+
|
|
97
|
+
handler.active = Math.max(0, handler.active - 1);
|
|
98
|
+
|
|
99
|
+
this.runtime.active_requests = Math.max(
|
|
100
|
+
0,
|
|
101
|
+
this.runtime.active_requests - 1,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
this.runtime.requests.delete(res.request_id);
|
|
105
|
+
|
|
106
|
+
await this.persist_runtime();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
cleanup_handlers = () => {
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
|
|
112
|
+
const MAX_IDLE = 24 * 60 * 60 * 1000; // 24 hours
|
|
113
|
+
|
|
114
|
+
for (const [route, handler] of Object.entries(this.runtime.handlers)) {
|
|
115
|
+
if (handler.active > 0) continue;
|
|
116
|
+
|
|
117
|
+
// Keep handlers we've gathered meaningful statistics for.
|
|
118
|
+
if (handler.completed >= 100) continue;
|
|
119
|
+
|
|
120
|
+
if (now - handler.last_seen > MAX_IDLE) {
|
|
121
|
+
delete this.runtime.handlers[route];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
get estimated_work() {
|
|
127
|
+
let total = 0;
|
|
128
|
+
|
|
129
|
+
for (const handler of Object.values(this.runtime.handlers)) {
|
|
130
|
+
total += handler.active * handler.average_duration;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Math.round(total);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get server_load() {
|
|
137
|
+
return {
|
|
138
|
+
active_requests: this.runtime.active_requests,
|
|
139
|
+
|
|
140
|
+
estimated_work_ms: this.estimated_work,
|
|
141
|
+
|
|
142
|
+
handlers: this.runtime.handlers,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default Resource;
|
|
@@ -119,12 +119,13 @@ class Headers extends Services {
|
|
|
119
119
|
headers["authorization"] = `Bearer ${authorization}`;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
let
|
|
122
|
+
let gp_profile = gp_services().profile;
|
|
123
|
+
let res = await fetch(`${gp_profile.url}/validate`, {
|
|
123
124
|
method: "post",
|
|
124
125
|
headers: {
|
|
125
126
|
"Content-Type": "application/json",
|
|
126
127
|
Accept: "application/json",
|
|
127
|
-
"x-api-version":
|
|
128
|
+
"x-api-version": gp_profile.api_version,
|
|
128
129
|
...headers,
|
|
129
130
|
},
|
|
130
131
|
body: JSON.stringify({}),
|
|
@@ -180,21 +181,23 @@ class Headers extends Services {
|
|
|
180
181
|
hst = hst.replace("10.0.2.2", "localhost");
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
let gp_profile = gp_services().profile;
|
|
185
|
+
|
|
183
186
|
let third_party =
|
|
184
|
-
xplatform !== this.platform_uri && !
|
|
187
|
+
xplatform !== this.platform_uri && !gp_profile.url.endsWith(hst);
|
|
185
188
|
|
|
186
|
-
if (
|
|
189
|
+
if (gp_profile.url.endsWith(hst)) {
|
|
187
190
|
headers["x-platform"] = xplatform;
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
let res = await fetch(
|
|
191
|
-
`${
|
|
194
|
+
`${gp_profile.url}/${third_party ? "third_party_me" : "me"}`,
|
|
192
195
|
{
|
|
193
196
|
method: "post",
|
|
194
197
|
headers: {
|
|
195
198
|
"Content-Type": "application/json",
|
|
196
199
|
Accept: "application/json",
|
|
197
|
-
"x-api-version":
|
|
200
|
+
"x-api-version": gp_profile.api_version,
|
|
198
201
|
...headers,
|
|
199
202
|
},
|
|
200
203
|
body: JSON.stringify(third_party ? { from: xplatform } : {}),
|
|
@@ -225,6 +228,8 @@ class Headers extends Services {
|
|
|
225
228
|
};
|
|
226
229
|
|
|
227
230
|
handle_security = async (name, request) => {
|
|
231
|
+
if (this.active_version === "*") return true;
|
|
232
|
+
|
|
228
233
|
let route = await this.get_route(name);
|
|
229
234
|
|
|
230
235
|
if (!route) {
|
|
@@ -250,11 +255,14 @@ class Headers extends Services {
|
|
|
250
255
|
|
|
251
256
|
let val;
|
|
252
257
|
if (route.config.security?.includes("auth_token")) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
+
if (authorisation?.startsWith("p")) {
|
|
259
|
+
val = await this.validate_api_key(authorisation);
|
|
260
|
+
} else
|
|
261
|
+
val = await this.validate_third_party(
|
|
262
|
+
xplatform || this.platform_uri,
|
|
263
|
+
authorisation,
|
|
264
|
+
request,
|
|
265
|
+
);
|
|
258
266
|
} else if (route.config.security?.includes("api_key")) {
|
|
259
267
|
val = await this.validate_api_key(api_key, authorisation);
|
|
260
268
|
}
|
|
@@ -42,6 +42,13 @@ class Route_table extends Headers {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
load_routes = async (routes) => {
|
|
45
|
+
if (this.active_version === "*") {
|
|
46
|
+
this.versions[this.active_version] = {
|
|
47
|
+
handler: routes,
|
|
48
|
+
};
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
for (let name in routes) {
|
|
46
53
|
let { handler, ...config } = routes[name];
|
|
47
54
|
|
|
@@ -181,6 +188,12 @@ class Route_table extends Headers {
|
|
|
181
188
|
// 🚀 EXECUTION PIPELINE
|
|
182
189
|
// =========================
|
|
183
190
|
execute = async (name, payload) => {
|
|
191
|
+
if (this.active_version === "*") {
|
|
192
|
+
let version = this.versions[this.active_version];
|
|
193
|
+
|
|
194
|
+
return await version.handler(payload);
|
|
195
|
+
}
|
|
196
|
+
|
|
184
197
|
let route = await this.get_route(name);
|
|
185
198
|
|
|
186
199
|
if (!route) {
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
import { DB } from "./Header.js";
|
|
2
2
|
|
|
3
|
-
let
|
|
4
|
-
let
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
let gp_services = () => {
|
|
4
|
+
let DEV = process.env.DEV;
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
profile: {
|
|
8
|
+
url: DEV
|
|
9
|
+
? "http://localhost:4000"
|
|
10
|
+
: "https://profile-api.savvyaisolution.com",
|
|
11
|
+
api_version: "v3",
|
|
12
|
+
uri: "profiles.savvyaisolution.com",
|
|
13
|
+
},
|
|
14
|
+
settings: {
|
|
15
|
+
url: DEV
|
|
16
|
+
? "http://localhost:4005"
|
|
17
|
+
: "https://settings-api.savvyaisolution.com",
|
|
18
|
+
api_version: "v1",
|
|
19
|
+
uri: "settings.savvyaisolution.com",
|
|
20
|
+
},
|
|
21
|
+
godprotocol: {
|
|
22
|
+
url: DEV
|
|
23
|
+
? "http://localhost:4001"
|
|
24
|
+
: "https://godprotocol-api.savvyaisolution.com",
|
|
25
|
+
api_version: "v1",
|
|
26
|
+
uri: "godprotocol.savvyaisolution.com",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
11
29
|
};
|
|
12
30
|
|
|
13
31
|
export { gp_services };
|
|
@@ -15,11 +33,14 @@ export { gp_services };
|
|
|
15
33
|
class Services {
|
|
16
34
|
constructor() {
|
|
17
35
|
this.services = new Object();
|
|
36
|
+
|
|
37
|
+
this.load_services(gp_services);
|
|
18
38
|
}
|
|
19
39
|
|
|
20
40
|
load_services = async (services) => {
|
|
21
41
|
if (typeof services === "function") services = await services();
|
|
22
|
-
|
|
42
|
+
|
|
43
|
+
this.services = { ...this.services, ...services };
|
|
23
44
|
};
|
|
24
45
|
|
|
25
46
|
get_token = async (service, id) => {
|
|
@@ -37,12 +58,13 @@ class Services {
|
|
|
37
58
|
try {
|
|
38
59
|
// debug(this.api_key, gp_services.profile);
|
|
39
60
|
|
|
40
|
-
let
|
|
61
|
+
let gp_profile = gp_services().profile;
|
|
62
|
+
let ftch = await fetch(`${gp_profile.url}/get_token`, {
|
|
41
63
|
method: "POST",
|
|
42
64
|
headers: {
|
|
43
65
|
"Content-Type": "application/json",
|
|
44
66
|
Accept: "application/json",
|
|
45
|
-
"x-api-version":
|
|
67
|
+
"x-api-version": gp_profile.api_version,
|
|
46
68
|
"x-api-key": this.api_key,
|
|
47
69
|
},
|
|
48
70
|
body: JSON.stringify({ platform_uri: service.uri, ...id }),
|
|
@@ -85,7 +107,7 @@ class Services {
|
|
|
85
107
|
"x-api-version": servic.api_version || "v1",
|
|
86
108
|
};
|
|
87
109
|
let token;
|
|
88
|
-
if (header) {
|
|
110
|
+
if (header?.token || header?.profile) {
|
|
89
111
|
if (header.token) {
|
|
90
112
|
token = header.token;
|
|
91
113
|
} else if (header.profile) {
|
|
@@ -98,10 +120,20 @@ class Services {
|
|
|
98
120
|
|
|
99
121
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
100
122
|
headers["x-platform"] = this.platform_uri;
|
|
101
|
-
} else {
|
|
102
|
-
headers["x-api-key"] = servic.api_key
|
|
123
|
+
} else if (servic.api_key) {
|
|
124
|
+
headers["x-api-key"] = servic.api_key;
|
|
103
125
|
}
|
|
104
126
|
|
|
127
|
+
if (
|
|
128
|
+
!headers["x-api-key"] &&
|
|
129
|
+
!headers["Authorization"] &&
|
|
130
|
+
!servic.opened
|
|
131
|
+
) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
meesage: "Service unauthorized",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
105
137
|
try {
|
|
106
138
|
let response = await fetch(`${servic.url}/${path}`, {
|
|
107
139
|
method: "POST",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import parseMultipart from "
|
|
2
|
-
import serveStaticFile from "
|
|
1
|
+
import parseMultipart from "./parse_multipart_data.js";
|
|
2
|
+
import serveStaticFile from "./serve_static_file.js";
|
|
3
3
|
|
|
4
4
|
const getBody = (req) => {
|
|
5
5
|
return new Promise((resolve, reject) => {
|
|
@@ -83,6 +83,8 @@ const respond = (result, res) => {
|
|
|
83
83
|
|
|
84
84
|
res.end(JSON.stringify(response));
|
|
85
85
|
|
|
86
|
+
res.gp.compute_response(res);
|
|
87
|
+
|
|
86
88
|
return response;
|
|
87
89
|
};
|
|
88
90
|
|
|
@@ -100,6 +102,11 @@ const version_middleware = async (req, res, routers) => {
|
|
|
100
102
|
const parsed_url = new URL(req.url, `http://${req.headers.host}`);
|
|
101
103
|
const pathname = parsed_url.pathname;
|
|
102
104
|
|
|
105
|
+
req.request_id = crypto.randomUUID();
|
|
106
|
+
routers.gp.incoming_request(req);
|
|
107
|
+
res.gp = routers.gp;
|
|
108
|
+
res.request_id = req.request_id;
|
|
109
|
+
|
|
103
110
|
// STATIC ROUTE DETECTION
|
|
104
111
|
if (routers.static_path && pathname.startsWith(routers.static_path)) {
|
|
105
112
|
return serveStaticFile(req, res);
|
|
@@ -121,17 +128,21 @@ const version_middleware = async (req, res, routers) => {
|
|
|
121
128
|
req.headers["x-platform"],
|
|
122
129
|
);
|
|
123
130
|
|
|
124
|
-
|
|
131
|
+
let versionRouter = await routers.get_version(version);
|
|
125
132
|
|
|
126
133
|
if (!versionRouter) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
version = "*";
|
|
135
|
+
versionRouter = await routers.get_version(version);
|
|
136
|
+
|
|
137
|
+
if (!versionRouter)
|
|
138
|
+
return respond(
|
|
139
|
+
{
|
|
140
|
+
ok: false,
|
|
141
|
+
message: "Invalid API version",
|
|
142
|
+
status: 400,
|
|
143
|
+
},
|
|
144
|
+
res,
|
|
145
|
+
);
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
let body = {};
|
|
@@ -189,7 +200,7 @@ const version_middleware = async (req, res, routers) => {
|
|
|
189
200
|
const db = await router.resolve_db(req);
|
|
190
201
|
|
|
191
202
|
// WEBHOOK PRIOR
|
|
192
|
-
if (!isWebhook) {
|
|
203
|
+
if (!isWebhook && version !== "*") {
|
|
193
204
|
error_stage = "Callback";
|
|
194
205
|
const webPrior = await router.handle_webhook_prior({
|
|
195
206
|
...req,
|
|
@@ -213,10 +224,11 @@ const version_middleware = async (req, res, routers) => {
|
|
|
213
224
|
gp: router.gp,
|
|
214
225
|
services: router.get_service,
|
|
215
226
|
method: req.method,
|
|
227
|
+
memory: router.gp.memory,
|
|
216
228
|
});
|
|
217
229
|
|
|
218
230
|
// WEBHOOK AFTER
|
|
219
|
-
if (!isWebhook) {
|
|
231
|
+
if (!isWebhook && version !== "*") {
|
|
220
232
|
error_stage = "Webhook";
|
|
221
233
|
await router.handle_webhook_after(req, result);
|
|
222
234
|
}
|