diff --git a/domain/resmon.js b/domain/resmon.js
index 51f1a11..f1ea0c1 100644
--- a/domain/resmon.js
+++ b/domain/resmon.js
@@ -9,14 +9,13 @@
start() {
const { interval } = application.resmon.config;
- setInterval(() => {
+ setTimeout(() => {
const stats = application.resmon.getStatistics();
- const { heapTotal, heapUsed, external, contexts, detached } = stats;
+ const { heapTotal, heapUsed, external } = stats;
const total = application.utils.bytesToSize(heapTotal);
const used = application.utils.bytesToSize(heapUsed);
const ext = application.utils.bytesToSize(external);
console.log(`Heap: ${used} of ${total}, ext: ${ext}`);
- console.log(`Contexts: ${contexts}, detached: ${detached}`);
}, interval);
}
});
diff --git a/lib/client.js b/lib/client.js
index 058dac4..55eefa7 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -50,13 +50,15 @@ class Client {
res.end();
}
- error(status, err) {
+ error(status, err, callId = err) {
const { req: { url }, res, connection } = this;
const reason = http.STATUS_CODES[status];
+ if (typeof err === 'number') err = undefined;
const error = err ? err.stack : reason;
const msg = status === 403 ? err.message : `${url} - ${error} - ${status}`;
application.logger.error(msg);
- const result = JSON.stringify({ result: 'error', reason });
+ const packet = { callback: callId, error: { message: reason } };
+ const result = JSON.stringify(packet);
if (connection) {
connection.send(result);
return;
@@ -66,24 +68,32 @@ class Client {
res.end(result);
}
- async rpc(method, args) {
+ message(data) {
+ const packet = JSON.parse(data);
+ const [callType, methodName] = Object.keys(packet);
+ const callId = packet[callType];
+ const args = packet[methodName];
+ this.rpc(callId, methodName, args);
+ }
+
+ async rpc(callId, method, args) {
const { res, connection } = this;
const { semaphore } = application.server;
try {
await semaphore.enter();
} catch {
- this.error(504);
+ this.error(504, callId);
return;
}
try {
const session = await application.auth.restore(this);
const proc = application.runMethod(method, session);
if (!proc) {
- this.error(404);
+ this.error(404, callId);
return;
}
if (!session && proc.access !== 'public') {
- this.error(403, new Error(`Forbidden: /api/${method}`));
+ this.error(403, new Error(`Forbidden: /api/${method}`), callId);
return;
}
const result = await proc.method(args);
@@ -91,11 +101,12 @@ class Client {
const session = application.auth.start(this, result.userId);
result.token = session.token;
}
- const data = JSON.stringify(result);
+ const packet = { callback: callId, result };
+ const data = JSON.stringify(packet);
if (connection) connection.send(data);
else res.end(data);
} catch (err) {
- this.error(500, err);
+ this.error(500, err, callId);
} finally {
semaphore.leave();
}
diff --git a/lib/server.js b/lib/server.js
index 48fb33a..41be437 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -9,7 +9,6 @@ const Client = require('./client.js');
const SHUTDOWN_TIMEOUT = 5000;
const LONG_RESPONSE = 30000;
-const METHOD_OFFSET = '/api/'.length;
const clients = new Map();
@@ -51,15 +50,13 @@ const listener = (req, res) => {
});
application.logger.log(`${method}\t${url}`);
- if (url.startsWith('/api/')) {
+ if (url === '/api') {
if (method !== 'POST') {
client.error(403, new Error(`Forbidden: ${url}`));
return;
}
- receiveBody(req).then(body => {
- const method = url.substring(METHOD_OFFSET);
- const args = JSON.parse(body);
- client.rpc(method, args);
+ receiveBody(req).then(data => {
+ client.message(data);
});
} else {
if (url === '/' && !req.connection.encrypted) {
@@ -84,9 +81,8 @@ class Server {
this.ws = new WebSocket.Server({ server: this.instance });
this.ws.on('connection', (connection, req) => {
const client = new Client(req, null, connection);
- connection.on('message', message => {
- const { method, args } = JSON.parse(message);
- client.rpc(method, args);
+ connection.on('message', data => {
+ client.message(data);
});
});
this.instance.listen(port, host);
diff --git a/static/.eslintrc.json b/static/.eslintrc.json
new file mode 100644
index 0000000..bd14a19
--- /dev/null
+++ b/static/.eslintrc.json
@@ -0,0 +1,5 @@
+{
+ "parserOptions": {
+ "sourceType": "module"
+ }
+}
diff --git a/static/console.js b/static/console.js
index f561a11..11cc39a 100644
--- a/static/console.js
+++ b/static/console.js
@@ -1,39 +1,8 @@
-'use strict';
+import { Metacom } from './metacom.js';
-// API Builder
-
-const socket = new WebSocket('wss://' + location.host);
-
-const buildAPI = (methods, socket = null) => {
- const api = {};
- for (const method of methods) {
- api[method] = (args = {}) => new Promise((resolve, reject) => {
- if (socket) {
- socket.send(JSON.stringify({ method, args }));
- socket.onmessage = event => {
- const obj = JSON.parse(event.data);
- if (obj.result !== 'error') resolve(obj);
- else reject(new Error(`Status Code: ${obj.reason}`));
- };
- } else {
- fetch(`/api/${method}`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(args),
- }).then(res => {
- const { status } = res;
- if (status === 200) resolve(res.json());
- else reject(new Error(`Status Code: ${status}`));
- });
- }
- });
- }
- return api;
-};
-
-let api = buildAPI(['status', 'signIn', 'introspection'], socket);
-
-// Console Emulation
+const metacom = new Metacom(location.host);
+const { api } = metacom;
+window.api = api;
const ALPHA_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const ALPHA_LOWER = 'abcdefghijklmnopqrstuvwxyz';
@@ -276,12 +245,12 @@ function commandLoop() {
const signIn = async () => {
try {
+ await metacom.load('status', 'signIn', 'introspection');
await api.status();
} catch (err) {
await api.signIn({ login: 'marcus', password: 'marcus' });
}
- const methods = await api.introspection();
- api = buildAPI(methods, socket);
+ await metacom.load('example');
};
window.addEventListener('load', () => {
diff --git a/static/index.html b/static/index.html
index 6be5c1b..595ef62 100644
--- a/static/index.html
+++ b/static/index.html
@@ -5,7 +5,7 @@
-
+
diff --git a/static/metacom.js b/static/metacom.js
new file mode 100644
index 0000000..afc5807
--- /dev/null
+++ b/static/metacom.js
@@ -0,0 +1,59 @@
+export class Metacom {
+ constructor(host) {
+ this.socket = new WebSocket('wss://' + host);
+ this.api = {};
+ this.callId = 0;
+ this.calls = new Map();
+ this.socket.onmessage = ({ data }) => {
+ try {
+ const packet = JSON.parse(data);
+ const { callback, event } = packet;
+ const callId = callback || event;
+ const [resolve, reject] = this.calls.get(callId);
+ if (packet.error) {
+ const { code, message } = packet.error;
+ const error = new Error(message);
+ error.code = code;
+ reject(error);
+ return;
+ }
+ resolve(packet.result);
+ } catch (err) {
+ console.error(err);
+ }
+ };
+ }
+
+ async load(...methods) {
+ for (const methodName of methods) {
+ this.api[methodName] = this.socketCall(methodName);
+ }
+ }
+
+ httpCall(methodName) {
+ return (args = {}) => {
+ const callId = ++this.callId;
+ const packet = { call: callId, [methodName]: args };
+ return fetch('/api', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(packet),
+ }).then(res => {
+ const { status } = res;
+ if (status === 200) return res.json().then(({ result }) => result);
+ throw new Error(`Status Code: ${status}`);
+ });
+ };
+ }
+
+ socketCall(methodName) {
+ return (args = {}) => {
+ const callId = ++this.callId;
+ return new Promise((resolve, reject) => {
+ this.calls.set(callId, [resolve, reject]);
+ const packet = { call: callId, [methodName]: args };
+ this.socket.send(JSON.stringify(packet));
+ });
+ };
+ }
+}
diff --git a/test/system.js b/test/system.js
index 9955cc4..fffec46 100644
--- a/test/system.js
+++ b/test/system.js
@@ -11,6 +11,8 @@ const PORT = 8000;
const START_TIMEOUT = 1000;
const TEST_TIMEOUT = 3000;
+let callId = 0;
+
console.log('System test started');
setTimeout(async () => {
worker.postMessage({ name: 'stop' });
@@ -24,8 +26,9 @@ const tasks = [
{ get: '/', status: 302 },
{ get: '/console.js' },
{
- post: '/api/signIn',
- data: { login: 'marcus', password: 'marcus' }
+ post: '/api',
+ method: 'signIn',
+ args: { login: 'marcus', password: 'marcus' }
}
];
@@ -42,8 +45,9 @@ const getRequest = task => {
request.method = 'POST';
request.path = task.post;
}
- if (task.data) {
- task.data = JSON.stringify(task.data);
+ if (task.args) {
+ const packet = { call: ++callId, [task.method]: task.args };
+ task.data = JSON.stringify(packet);
request.headers = {
'Content-Type': 'application/json',
'Content-Length': task.data.length