Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

metacom

Package Overview
Dependencies
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

metacom - npm Package Compare versions

Comparing version 0.1.0-alpha.6 to 0.1.0-alpha.7

lib/channel.js

13

dist/metacom.js
class MetacomError extends Error {
constructor(message, code) {
constructor({ message, code }) {
super(message);

@@ -55,5 +55,3 @@ this.code = code;

if (packet.error) {
const { message, code } = packet.error;
const error = new MetacomError(message, code);
reject(error);
reject(new MetacomError(packet.error));
return;

@@ -111,3 +109,8 @@ }

const { status } = res;
if (status === 200) return res.json().then(({ result }) => result);
if (status === 200) {
return res.json().then(packet => {
if (packet.error) throw new MetacomError(packet.error);
return packet.result;
});
}
throw new Error(`Status Code: ${status}`);

@@ -114,0 +117,0 @@ });

'use strict';
const http = require('http');
const path = require('path');
const https = require('https');
const transport = { http, https };
const WebSocket = require('ws');
const MIME_TYPES = {
html: 'text/html; charset=UTF-8',
json: 'application/json; charset=UTF-8',
js: 'application/javascript; charset=UTF-8',
css: 'text/css',
png: 'image/png',
ico: 'image/x-icon',
svg: 'image/svg+xml',
};
class MetacomError extends Error {
constructor({ message, code }) {
super(message);
this.code = code;
}
}
const HEADERS = {
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'Strict-Transport-Security': 'max-age=31536000; includeSubdomains; preload',
'Access-Control-Allow-Origin': '*',
const fetch = (url, options) => {
const dest = new URL(url);
return new Promise((resolve, reject) => {
const protocol = transport[dest.protocol.slice(0, -1)];
const req = protocol.request(url, options, async res => {
const buffers = [];
for await (const chunk of res) {
buffers.push(chunk);
}
resolve(Buffer.concat(buffers).toString());
});
req.on('error', reject);
req.write(options.body);
req.end();
});
};
class Client {
constructor(req, res, connection, application) {
this.req = req;
this.res = res;
this.ip = req.socket.remoteAddress;
this.connection = connection;
this.application = application;
module.exports = class Metacom {
constructor(url) {
this.url = url;
this.socket = new WebSocket(url);
this.api = {};
this.callId = 0;
this.calls = new Map();
this.socket.addEventListener('message', ({ data }) => {
try {
const packet = JSON.parse(data);
const { callback, event } = packet;
const callId = callback || event;
const promised = this.calls.get(callId);
if (!promised) return;
const [resolve, reject] = promised;
if (packet.error) {
reject(new MetacomError(packet.error));
return;
}
resolve(packet.result);
} catch (err) {
console.error(err);
}
});
}
static() {
const { req, res, ip, application } = this;
const { url, method } = req;
const filePath = url === '/' ? '/index.html' : url;
const fileExt = path.extname(filePath).substring(1);
const mimeType = MIME_TYPES[fileExt] || MIME_TYPES.html;
res.writeHead(200, { ...HEADERS, 'Content-Type': mimeType });
if (res.writableEnded) return;
const data = application.getStaticFile(filePath);
if (data) {
res.end(data);
application.logger.access(`${ip}\t${method}\t${url}`);
return;
}
this.error(404);
ready() {
return new Promise(resolve => {
if (this.socket.readyState === WebSocket.OPEN) resolve();
else this.socket.addEventListener('open', resolve);
});
}
redirect(location) {
const { res } = this;
if (res.headersSent) return;
res.writeHead(302, { Location: location });
res.end();
}
error(code, err, callId = err) {
const { req, res, connection, ip, application } = this;
const { url, method } = req;
const status = http.STATUS_CODES[code];
if (typeof err === 'number') err = undefined;
const reason = err ? err.stack : status;
application.logger.error(`${ip}\t${method}\t${url}\t${code}\t${reason}`);
if (connection) {
const packet = { callback: callId, error: { code, message: status } };
connection.send(JSON.stringify(packet));
return;
async load(...interfaces) {
const introspect = this.httpCall('system')('introspect');
const introspection = await introspect(interfaces);
const available = Object.keys(introspection);
for (const interfaceName of interfaces) {
if (!available.includes(interfaceName)) continue;
const methods = {};
const iface = introspection[interfaceName];
const request = this.socketCall(interfaceName);
const methodNames = Object.keys(iface);
for (const methodName of methodNames) {
methods[methodName] = request(methodName);
}
this.api[interfaceName] = methods;
}
if (res.writableEnded) return;
res.writeHead(status, { 'Content-Type': MIME_TYPES.json });
const packet = { code, error: status };
res.end(JSON.stringify(packet));
}
message(data) {
let packet;
try {
packet = JSON.parse(data);
} catch (err) {
this.error(500, new Error('JSON parsing error'));
return;
}
const [callType, target] = Object.keys(packet);
const callId = packet[callType];
const args = packet[target];
if (callId && args) {
const [interfaceName, methodName] = target.split('/');
this.rpc(callId, interfaceName, methodName, args);
return;
}
this.error(500, new Error('Packet structure error'));
httpCall(iname, ver) {
return methodName => (args = {}) => {
const callId = ++this.callId;
const interfaceName = ver ? `${iname}.${ver}` : iname;
const target = interfaceName + '/' + methodName;
const packet = { call: callId, [target]: args };
const dest = new URL(this.url);
const protocol = dest.protocol === 'ws:' ? 'http' : 'https';
const url = `${protocol}://${dest.host}/api`;
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(packet),
}).then(json => {
const packet = JSON.parse(json);
if (packet.error) throw new MetacomError(packet.error);
return packet.result;
});
};
}
async rpc(callId, interfaceName, methodName, args) {
const { res, connection, ip, application } = this;
const { semaphore } = application.server;
try {
await semaphore.enter();
} catch {
this.error(504, callId);
return;
}
const [iname, ver = '*'] = interfaceName.split('.');
try {
let session = await application.auth.restore(this);
const proc = application.getMethod(iname, ver, methodName, session);
if (!proc) {
this.error(404, callId);
return;
}
if (!session && proc.access !== 'public') {
this.error(403, callId);
return;
}
const result = await proc.method(args);
const userId = result ? result.userId : undefined;
if (!session && userId && proc.access === 'public') {
session = application.auth.start(this, userId);
result.token = session.token;
}
const data = JSON.stringify({ callback: callId, result });
if (connection) connection.send(data);
else res.end(data);
const token = session ? session.token : 'anonymous';
const record = `${ip}\t${token}\t${interfaceName}/${methodName}`;
application.logger.access(record);
} catch (err) {
this.error(500, err, callId);
} finally {
semaphore.leave();
}
socketCall(iname, ver) {
return methodName => async (args = {}) => {
const callId = ++this.callId;
const interfaceName = ver ? `${iname}.${ver}` : iname;
const target = interfaceName + '/' + methodName;
await this.ready();
return new Promise((resolve, reject) => {
this.calls.set(callId, [resolve, reject]);
const packet = { call: callId, [target]: args };
this.socket.send(JSON.stringify(packet));
});
};
}
}
module.exports = Client;
};

@@ -11,3 +11,3 @@ 'use strict';

const Semaphore = require('./semaphore.js');
const Client = require('./client.js');
const Channel = require('./channel.js');

@@ -36,3 +36,3 @@ const SHUTDOWN_TIMEOUT = 5000;

this.application = application;
this.clients = new Map();
this.channels = new Map();
const { ports, host, concurrency, queue } = config;

@@ -48,5 +48,5 @@ this.semaphore = new Semaphore(concurrency, queue.size, queue.timeout);

this.ws.on('connection', (connection, req) => {
const client = new Client(req, null, connection, application);
const channel = new Channel(req, null, connection, application);
connection.on('message', data => {
client.message(data);
channel.message(data);
});

@@ -58,7 +58,7 @@ });

listener(req, res) {
const { clients } = this;
const { channels } = this;
let finished = false;
const { method, url, connection } = req;
const client = new Client(req, res, null, this.application);
clients.set(connection, client);
const channel = new Channel(req, res, null, this.application);
channels.set(connection, channel);

@@ -68,4 +68,4 @@ const timer = setTimeout(() => {

finished = true;
clients.delete(connection);
client.error(504);
channels.delete(connection);
channel.error(504);
}, LONG_RESPONSE);

@@ -77,3 +77,3 @@

clearTimeout(timer);
clients.delete(connection);
channels.delete(connection);
});

@@ -83,3 +83,3 @@

if (method !== 'POST') {
client.error(403);
channel.error(403);
return;

@@ -89,6 +89,6 @@ }

data => {
client.message(data);
channel.message(data);
},
err => {
client.error(500, err);
channel.error(500, err);
}

@@ -100,13 +100,13 @@ );

const port = sample(this.ports);
client.redirect(`https://${host}:${port}/`);
channel.redirect(`https://${host}:${port}/`);
}
client.static();
channel.static();
}
}
closeClients() {
const { clients } = this;
for (const [connection, client] of clients.entries()) {
clients.delete(connection);
client.error(503);
closeChannels() {
const { channels } = this;
for (const [connection, channel] of channels.entries()) {
channels.delete(connection);
channel.error(503);
connection.destroy();

@@ -121,3 +121,3 @@ }

await timeout(SHUTDOWN_TIMEOUT);
this.closeClients();
this.closeChannels();
}

@@ -124,0 +124,0 @@ }

'use strict';
module.exports = require('./lib/metacom.js');
module.exports = require('./lib/client.js');
module.exports.Server = require('./lib/server.js');
{
"name": "metacom",
"version": "0.1.0-alpha.6",
"version": "0.1.0-alpha.7",
"author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>",

@@ -48,3 +48,3 @@ "description": "Communication protocol for Metarhia stack with rpc, events, binary streams, memory and db access",

"devDependencies": {
"eslint": "^7.7.0",
"eslint": "^7.8.1",
"eslint-config-metarhia": "^7.0.1",

@@ -51,0 +51,0 @@ "eslint-config-prettier": "^6.11.0",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc