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 2.0.1 to 2.0.2

lib/ws.js

11

CHANGELOG.md

@@ -5,2 +5,10 @@ # Changelog

## [2.0.2][] - 2021-09-11
- Rework Channel and Server
- Decompose Channel to WsChannel and HttpChannel
- Move event handlers from Server to WsChannel and HttpChannel
- Return after error to avoid double reply and logging
- Update typings
## [2.0.1][] - 2021-09-03

@@ -146,3 +154,4 @@

[unreleased]: https://github.com/metarhia/metacom/compare/v2.0.1...HEAD
[unreleased]: https://github.com/metarhia/metacom/compare/v2.0.2...HEAD
[2.0.2]: https://github.com/metarhia/metacom/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/metarhia/metacom/compare/v2.0.0...v2.0.1

@@ -149,0 +158,0 @@ [2.0.0]: https://github.com/metarhia/metacom/compare/v1.8.1...v2.0.0

115

lib/channel.js
'use strict';
const http = require('http');
const metautil = require('metautil');
const transport = require('./transport.js');
const { MIME_TYPES, HEADERS } = transport.http;
const { COOKIE_DELETE, COOKIE_HOST, TOKEN } = transport.http;

@@ -59,3 +55,3 @@ const EMPTY_PACKET = Buffer.from('{}');

}
channel.connection.send(JSON.stringify(packet));
channel.send(packet);
}

@@ -81,11 +77,9 @@

class Channel {
constructor(req, res, connection, application) {
constructor(application, req, res) {
this.application = application;
this.req = req;
this.res = res;
this.ip = req.socket.remoteAddress;
this.connection = connection;
this.application = application;
const client = new Client();
this.client = client;
channels.set(client, this);
this.client = new Client();
channels.set(this.client, this);
this.session = null;

@@ -96,45 +90,6 @@ this.restoreSession();

get token() {
if (this.session === null) return 'anonymous';
if (this.session === null) return '';
return this.session.token;
}
redirect(location) {
const { res } = this;
if (res.headersSent) return;
res.writeHead(302, { Location: location, ...HEADERS });
res.end();
}
send(ext, data) {
const { res } = this;
const mimeType = MIME_TYPES[ext] || MIME_TYPES.html;
res.writeHead(200, { ...HEADERS, 'Content-Type': mimeType });
res.end(data);
}
options() {
const { res } = this;
if (res.headersSent) return;
res.writeHead(200, HEADERS);
res.end();
}
error(code, err = null, callId) {
const { req, res, connection, ip, application } = this;
const { url, method } = req;
const status = http.STATUS_CODES[code];
const reason = err !== null ? err.stack : status;
application.console.error(`${ip}\t${method}\t${url}\t${code}\t${reason}`);
const message = status || err.message;
const error = { message, code };
if (connection) {
connection.send(JSON.stringify({ callback: callId, error }));
return;
}
if (res.writableEnded) return;
const httpCode = status ? code : 500;
res.writeHead(httpCode, { 'Content-Type': MIME_TYPES.json, ...HEADERS });
res.end(JSON.stringify({ error }));
}
message(data) {

@@ -188,29 +143,11 @@ if (Buffer.compare(EMPTY_PACKET, data) === 0) {

this.error(code, err, callId);
return;
} finally {
proc.leave();
}
this.reply(callId, result);
const record = `${this.ip}\t${interfaceName}/${methodName}`;
application.console.log(record);
}
async hook(proc, interfaceName, methodName, args) {
const { application, client, res } = this;
const callId = -1;
if (!proc) {
this.error(404, null, callId);
if (typeof result === 'object' && result.constructor.name === 'Error') {
this.error(result.code, result, callId);
return;
}
const context = { client };
let result = null;
try {
result = await proc.invoke(context, { method: methodName, args });
} catch (err) {
this.error(500, err, callId);
}
const data = JSON.stringify(result);
if (!res.writableEnded) {
res.writeHead(200, { 'Content-Type': MIME_TYPES.json, ...HEADERS });
res.end(data);
}
this.send({ callback: callId, result });
const record = `${this.ip}\t${interfaceName}/${methodName}`;

@@ -220,26 +157,2 @@ application.console.log(record);

reply(callId, result) {
const { res, connection } = this;
if (typeof result === 'object' && result.constructor.name === 'Error') {
this.error(result.code, result, callId);
return;
}
const data = JSON.stringify({ callback: callId, result });
if (connection) {
connection.send(data);
} else {
res.writeHead(200, { 'Content-Type': MIME_TYPES.json, ...HEADERS });
res.end(data);
}
}
startSession() {
const token = this.application.auth.generateToken();
const host = metautil.parseHost(this.req.headers.host);
const cookie = `${TOKEN}=${token}; ${COOKIE_HOST}=${host}`;
const session = this.application.auth.startSession();
if (this.res) this.res.setHeader('Set-Cookie', cookie);
return session;
}
async restoreSession() {

@@ -256,10 +169,2 @@ const { cookie } = this.req.headers;

deleteSession() {
const { token } = this;
if (token === 'anonymous') return;
const host = metautil.parseHost(this.req.headers.host);
this.res.setHeader('Set-Cookie', COOKIE_DELETE + host);
this.application.auth.deleteSession(token);
}
destroy() {

@@ -266,0 +171,0 @@ channels.delete(this.client);

'use strict';
const http = require('http');
const metautil = require('metautil');
const { Channel } = require('./channel.js');
const MIME_TYPES = {

@@ -29,2 +33,91 @@ html: 'text/html; charset=UTF-8',

module.exports = { MIME_TYPES, HEADERS, COOKIE_DELETE, COOKIE_HOST, TOKEN };
class HttpChannel extends Channel {
constructor(application, req, res) {
super(application, req, res);
res.on('close', () => {
this.destroy();
});
}
write(data, httpCode = 200, ext = 'json') {
const { res } = this;
if (res.writableEnded) return;
const mimeType = MIME_TYPES[ext] || MIME_TYPES.html;
res.writeHead(httpCode, { ...HEADERS, 'Content-Type': mimeType });
res.end(data);
}
send(obj, httpCode = 200) {
const data = JSON.stringify(obj);
this.write(data, httpCode, 'json');
}
error(code, err = null, callId) {
const { req, ip, application } = this;
const { url, method } = req;
const status = http.STATUS_CODES[code];
const reason = err ? err.stack : status;
application.console.error(`${ip}\t${method}\t${url}\t${code}\t${reason}`);
const message = status || 'Unknown error';
const httpCode = status ? code : 500;
const error = { message, code };
if (callId) this.send({ callback: callId, error });
else this.send({ error }, httpCode);
}
redirect(location) {
const { res } = this;
if (res.headersSent) return;
res.writeHead(302, { Location: location, ...HEADERS });
res.end();
}
options() {
const { res } = this;
if (res.headersSent) return;
res.writeHead(200, HEADERS);
res.end();
}
async hook(proc, interfaceName, methodName, args) {
const { application, client } = this;
const callId = -1;
if (!proc) {
this.error(404, null, callId);
return;
}
const context = { client };
let result = null;
try {
result = await proc.invoke(context, { method: methodName, args });
} catch (err) {
this.error(500, err, callId);
return;
}
this.send(result);
const record = `${this.ip}\t${interfaceName}/${methodName}`;
application.console.log(record);
}
startSession() {
const token = this.application.auth.generateToken();
const host = metautil.parseHost(this.req.headers.host);
const cookie = `${TOKEN}=${token}; ${COOKIE_HOST}=${host}`;
const session = this.application.auth.startSession();
if (this.res) this.res.setHeader('Set-Cookie', cookie);
return session;
}
deleteSession() {
const { token } = this;
if (token === 'anonymous') return;
const host = metautil.parseHost(this.req.headers.host);
this.res.setHeader('Set-Cookie', COOKIE_DELETE + host);
this.application.auth.deleteSession(token);
}
}
const createChannel = (application, req, res) =>
new HttpChannel(application, req, res);
module.exports = { createChannel };

@@ -9,4 +9,5 @@ 'use strict';

const ws = require('ws');
const { Channel, channels } = require('./channel.js');
const { channels } = require('./channel.js');
const { serveStatic } = require('./static.js');
const transport = require('./transport.js');

@@ -46,5 +47,5 @@ const SHORT_TIMEOUT = 500;

const { protocol, timeouts, nagle = true } = config;
const transport = protocol === 'http' || this.balancer ? http : https;
const proto = protocol === 'http' || this.balancer ? http : https;
const listener = this.listener.bind(this);
this.server = transport.createServer({ ...application.cert }, listener);
this.server = proto.createServer({ ...application.cert }, listener);
if (!nagle) {

@@ -59,10 +60,4 @@ this.server.on('connection', (socket) => {

this.ws = new ws.Server({ server: this.server });
this.ws.on('connection', async (connection, req) => {
const channel = await new Channel(req, null, connection, application);
connection.on('message', (data) => {
channel.message(data);
});
connection.on('close', () => {
channel.destroy();
});
this.ws.on('connection', (connection, req) => {
transport.ws.createChannel(application, req, connection);
});

@@ -79,13 +74,5 @@ this.ws.on('error', (err) => {

async listener(req, res) {
let finished = false;
listener(req, res) {
const { url } = req;
const channel = await new Channel(req, res, null, this.application);
res.on('close', () => {
if (finished) return;
finished = true;
channel.destroy();
});
const channel = transport.http.createChannel(this.application, req, res);
if (this.balancer) {

@@ -98,3 +85,2 @@ const host = metautil.parseHost(req.headers.host);

}
if (url.startsWith('/api')) this.request(channel);

@@ -101,0 +87,0 @@ else serveStatic(channel);

@@ -6,10 +6,9 @@ 'use strict';

const serveStatic = (channel) => {
const { req, res, ip, application } = channel;
const { req, res, application } = channel;
if (res.writableEnded) return;
const { url, method } = req;
application.console.log(`${ip}\t${method}\t${url}`);
const { url } = req;
const filePath = url === '/' ? '/index.html' : url;
const fileExt = path.extname(filePath).substring(1);
const data = application.getStaticFile(filePath);
if (data) channel.send(fileExt, data);
if (data) channel.write(data, 200, fileExt);
else channel.error(404);

@@ -16,0 +15,0 @@ };

@@ -5,2 +5,3 @@ 'use strict';

http: require('./http.js'),
ws: require('./ws.js'),
};
import { EventEmitter } from 'events';
import { ClientRequest, ServerResponse } from 'http';
import WebSocket from 'ws';
import { Semaphore } from 'metautil';

@@ -26,1 +29,84 @@ export interface MetacomError extends Error {

}
export interface ServerConfig {
concurrency: number;
host: string;
balancer: boolean;
protocol: string;
ports: Array<number>;
queue: object;
}
export class Session {
constructor(token: string, channel: Channel, data: any);
}
export class Channel {
application: object;
req: ClientRequest;
res: ServerResponse;
ip: string;
client: Metacom;
session?: Session;
constructor(application: object, req: ClientRequest, res: ServerResponse);
message(data: string): void;
prc(
callId: number,
interfaceName: string,
methodName: string,
args: []
): Promise<void>;
restoreSession(): Promise<Session | null>;
destroy(): void;
error(code: number, err?: boolean, callId?: number): void;
}
export class HttpChannel extends Channel {
write(data: any, httpCode?: number, ext?: string): void;
send(obj: object, httpCode?: number): void;
redirect(location: string): void;
options(): void;
hook(
proc: object,
interfaceName: string,
methodName: string,
args: Array<any>
): Promise<void>;
startSession(): Session;
deleteSession(): void;
}
export class WsChannel extends Channel {
connection: WebSocket;
constructor(application: object, req: ClientRequest, connection: WebSocket);
write(data: any): void;
send(obj: object): void;
redirect(location: string): void;
options(): void;
hook(
proc: object,
interfaceName: string,
methodName: string,
args: Array<any>
): Promise<void>;
startSession(): Session;
deleteSession(): void;
}
export class Server {
config: ServerConfig;
application: object;
semaphore: Semaphore;
balancer: boolean;
port: number;
server?: any;
ws?: any;
protocol: string;
host: string;
constructor(config: ServerConfig, application: object);
bind(): void;
listener(req: ClientRequest, res: ServerResponse): void;
request(channel: Channel): void;
closeChannels(): void;
close(): Promise<void>;
}
{
"name": "metacom",
"version": "2.0.1",
"version": "2.0.2",
"author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>",

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

"dependencies": {
"metautil": "^3.5.9",
"ws": "^8.2.0"
"metautil": "^3.5.11",
"ws": "^8.2.2"
},
"devDependencies": {
"@types/node": "^16.6.2",
"@types/node": "^16.9.1",
"@types/ws": "^7.4.7",

@@ -66,8 +66,8 @@ "eslint": "^7.31.0",

"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-prettier": "^4.0.0",
"metatests": "^0.7.2",
"prettier": "^2.3.2",
"typescript": "^4.3.5"
"prettier": "^2.4.0",
"typescript": "^4.4.3"
}
}
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