Compare commits

..

43 Commits

Author SHA1 Message Date
a8a91b4db2 1.0.42 2019-04-22 23:23:36 +02:00
67c4b06c4d fix(core): update 2019-04-22 23:23:36 +02:00
7693b52066 1.0.41 2019-04-22 23:11:52 +02:00
30a02ae48b fix(core): update 2019-04-22 23:11:51 +02:00
241182ed2e 1.0.40 2019-04-22 22:04:53 +02:00
3d82038ec3 fix(core): update 2019-04-22 22:04:52 +02:00
300d62ed12 1.0.39 2019-04-22 13:06:02 +02:00
a5e849aa17 fix(core): update 2019-04-22 13:06:01 +02:00
83807d7c5c 1.0.38 2019-04-22 09:58:36 +02:00
39d3a0f2f8 fix(core): update 2019-04-22 09:58:36 +02:00
904a48d414 1.0.37 2019-04-12 20:50:44 +02:00
e2acb28756 1.0.36 2019-04-11 18:59:46 +02:00
92e4379bd2 fix(core): update 2019-04-11 18:59:45 +02:00
e0ce732ee1 1.0.35 2019-04-11 18:57:24 +02:00
98be0f036c fix(core): update 2019-04-11 18:57:23 +02:00
54fca17142 1.0.34 2019-04-11 18:50:43 +02:00
1a7634e8db fix(core): update 2019-04-11 18:50:43 +02:00
8830b825ac 1.0.33 2019-04-11 18:32:36 +02:00
123324bf43 fix(core): update 2019-04-11 18:32:36 +02:00
4761ff31cf 1.0.32 2019-04-11 18:31:21 +02:00
430c3ea13a fix(core): update 2019-04-11 18:31:21 +02:00
6dd3782b0d 1.0.31 2019-04-11 18:16:14 +02:00
06aa721247 1.0.30 2019-04-11 17:52:02 +02:00
f728c56016 fix(core): update 2019-04-11 17:52:01 +02:00
f512acdfaa 1.0.29 2019-04-11 16:53:28 +02:00
331a098851 fix(core): update 2019-04-11 16:53:28 +02:00
a8053a6e20 1.0.28 2019-04-11 11:36:57 +02:00
a62f3eb750 fix(core): update 2019-04-11 11:36:57 +02:00
4b53044e8f 1.0.27 2019-04-11 11:15:09 +02:00
2db2c80130 fix(core): update 2019-04-11 11:15:09 +02:00
8b23eaf194 1.0.26 2019-04-11 11:11:46 +02:00
a03c54001f fix(core): update 2019-04-11 11:11:45 +02:00
7bc404ba21 1.0.25 2019-01-31 02:52:18 +01:00
ac2a1559b0 fix(core): update 2019-01-31 02:52:18 +01:00
57b37cb327 1.0.24 2019-01-30 11:01:49 +01:00
8b17e814cc fix(dependencies): update 2019-01-30 11:01:48 +01:00
f4b8cde347 fix(dependencies): update 2019-01-30 10:56:31 +01:00
3189d4d274 1.0.23 2018-05-30 16:34:07 +02:00
c2d134a6ea fix(websocket): switch to all websocket infrastructure 2018-05-30 16:34:06 +02:00
7552dc1e9f 1.0.22 2018-05-28 12:07:26 +02:00
fe91459510 fix(core): update to transparent universe 2018-05-28 12:07:25 +02:00
0e0ffb7634 1.0.21 2018-05-26 13:44:32 +02:00
37bb052774 fix(UniverseMessage / UniverseChannel): better authentication for of messages for channels 2018-05-26 13:44:32 +02:00
24 changed files with 2229 additions and 1404 deletions

View File

@ -3,7 +3,7 @@ image: hosttoday/ht-docker-node:npmci
cache:
paths:
- .yarn/
- .npmci_cache/
key: "$CI_BUILD_STAGE"
stages:
@ -26,8 +26,9 @@ mirror:
snyk:
stage: security
script:
- npmci command yarn global add snyk
- npmci command yarn install --ignore-scripts
- npmci npm prepare
- npmci command npm install -g snyk
- npmci command npm install --ignore-scripts
- npmci command snyk test
tags:
- docker
@ -36,21 +37,11 @@ snyk:
# ====================
# test stage
# ====================
testLEGACY:
stage: test
script:
- npmci node install legacy
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv
allow_failure: true
testLTS:
stage: test
script:
- npmci npm prepare
- npmci node install lts
- npmci npm install
- npmci npm test
@ -62,6 +53,7 @@ testLTS:
testSTABLE:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
@ -117,8 +109,10 @@ pages:
image: hosttoday/ht-docker-node:npmci
stage: metadata
script:
- npmci command yarn global add npmpage
- npmci command npmpage
- npmci command npm install -g typedoc typescript
- npmci npm prepare
- npmci npm install
- npmci command typedoc --module "commonjs" --target "ES2016" --out public/ ts/
tags:
- docker
- notpriv
@ -128,3 +122,4 @@ pages:
expire_in: 1 week
paths:
- public
allow_failure: true

19
license Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2018 Lossless GmbH (hello@lossless.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,9 +1,17 @@
{
"gitzone": {
"module": {
"githost": "gitlab.com",
"gitscope": "pushrocks",
"gitrepo": "smartuniverse",
"shortDescription": "messaging service for micro services",
"npmPackagename": "@pushrocks/smartuniverse",
"license": "MIT",
"projectDomain": "push.rocks"
}
},
"npmci": {
"npmGlobalTools": [
"@gitzone/npmts",
"ts-node"
],
"npmGlobalTools": [],
"npmAccessLevel": "public"
}
}
}

2695
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@pushrocks/smartuniverse",
"version": "1.0.20",
"version": "1.0.42",
"private": false,
"description": "messaging service for your micro services",
"main": "dist/index.js",
@ -8,28 +8,32 @@
"author": "Lossless GmbH",
"license": "MIT",
"scripts": {
"test": "(ts-node -O '{\"lib\": [ \"es2015\" ]}' test/test.ts)",
"build": "(npmts)",
"test": "(tstest test/)",
"build": "(tsbuild)",
"format": "(gitzone format)"
},
"devDependencies": {
"@types/node": "^9.6.0",
"tapbundle": "^2.0.0",
"ts-node": "^6.0.3",
"typescript": "^2.8.3"
"@gitzone/tsbuild": "^2.1.8",
"@gitzone/tstest": "^1.0.20",
"@pushrocks/tapbundle": "^3.0.9",
"@types/node": "^11.13.4",
"tslint": "^5.15.0",
"tslint-config-prettier": "^1.18.0"
},
"peerDependencies": {
"rxjs": "*"
},
"dependencies": {
"@pushrocks/smartcli": "^3.0.1",
"lik": "^2.0.5",
"nodehash": "^1.0.4",
"rxjs": "^5.5.8",
"smartdelay": "^1.0.4",
"smartexpress": "^1.0.21",
"smartfile": "^4.2.28",
"smartq": "^1.1.8",
"smartrequest": "^1.0.8",
"smartrx": "^1.0.5",
"smartsocket": "^1.1.19",
"smarttime": "^2.0.0"
"@pushrocks/lik": "^3.0.5",
"@pushrocks/smartdelay": "^2.0.3",
"@pushrocks/smartexpress": "^3.0.18",
"@pushrocks/smartfile": "^7.0.2",
"@pushrocks/smarthash": "^2.0.4",
"@pushrocks/smartpromise": "^3.0.2",
"@pushrocks/smartrequest": "^1.1.14",
"@pushrocks/smartrx": "^2.0.3",
"@pushrocks/smartsocket": "^1.1.27",
"@pushrocks/smarttime": "^3.0.7",
"@pushrocks/smartunique": "^3.0.1"
}
}

View File

@ -1,33 +1,56 @@
# smartuniverse
# @pushrocks/smartuniverse
messaging service for your micro services
messaging service for micro services
## Availabililty
## Availabililty and Links
[![npm](https://pushrocks.gitlab.io/assets/repo-button-npm.svg)](https://www.npmjs.com/package/@pushrocks/smartuniverse)
[![git](https://pushrocks.gitlab.io/assets/repo-button-git.svg)](https://GitLab.com/pushrocks/smartuniverse)
[![git](https://pushrocks.gitlab.io/assets/repo-button-mirror.svg)](https://github.com/pushrocks/smartuniverse)
[![docs](https://pushrocks.gitlab.io/assets/repo-button-docs.svg)](https://pushrocks.gitlab.io/smartuniverse/)
- [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartuniverse)
- [gitlab.com (source)](https://gitlab.com/pushrocks/smartuniverse)
- [github.com (source mirror)](https://github.com/pushrocks/smartuniverse)
- [docs (typedoc)](https://pushrocks.gitlab.io/smartuniverse/)
## Status for master
[![build status](https://GitLab.com/pushrocks/smartuniverse/badges/master/build.svg)](https://GitLab.com/pushrocks/smartuniverse/commits/master)
[![coverage report](https://GitLab.com/pushrocks/smartuniverse/badges/master/coverage.svg)](https://GitLab.com/pushrocks/smartuniverse/commits/master)
[![build status](https://gitlab.com/pushrocks/smartuniverse/badges/master/build.svg)](https://gitlab.com/pushrocks/smartuniverse/commits/master)
[![coverage report](https://gitlab.com/pushrocks/smartuniverse/badges/master/coverage.svg)](https://gitlab.com/pushrocks/smartuniverse/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/@pushrocks/smartuniverse.svg)](https://www.npmjs.com/package/@pushrocks/smartuniverse)
[![bitHound Dependencies](https://www.bithound.io/github/pushrocks/smartuniverse/badges/dependencies.svg)](https://www.bithound.io/github/pushrocks/smartuniverse/master/dependencies/npm)
[![bitHound Code](https://www.bithound.io/github/pushrocks/smartuniverse/badges/code.svg)](https://www.bithound.io/github/pushrocks/smartuniverse)
[![Known Vulnerabilities](https://snyk.io/test/npm/@pushrocks/smartuniverse/badge.svg)](https://snyk.io/test/npm/@pushrocks/smartuniverse)
[![TypeScript](https://img.shields.io/badge/TypeScript-2.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%206.x.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
[![TypeScript](https://img.shields.io/badge/TypeScript->=%203.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-prettier-ff69b4.svg)](https://prettier.io/)
## Usage
Use TypeScript for best in class instellisense.
For further information read the linked docs at the top of this README.
### What is smartuniverse all about?
> licensed | **©** [Lossless GmbH](https://lossless.gmbh)
Think WhatsApp, but for your microservices architecture. It allows your services to securely talk to each other in **private, shielded channels** without having to expose anything to the outside world. This allows the use of **reactive programming across your entire stack**.
### Server side
every universe has a server that manages messages.
Think Kafka, but without Kafka.
```typescript
import * as smartuniverse from '@pushrocks/smartuniverse';
const myUniverse = new smartuniverse.Universe({
messageExpiryInMilliseconds: 60000 // the standard time in milliseconds until a message expires
});
// create as many channels as you like
myUniverse.addChannel('awesomeChannel', 'awesomeChannelPass');
myUniverse.addChannel('awesomeChannel2', 'jhkjhfsdf87eerkjslkfja9');
myUniverse.start(8765); // start the server and provide the port on which to listen on
```
### Client side
All your microservices represents clients in the universe that may talk to each other using the universe server.
For further information read the linked docs at the top of this readme.
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
[![repo-footer](https://pushrocks.gitlab.io/assets/repo-footer.svg)](https://push.rocks)
[![repo-footer](https://pushrocks.gitlab.io/assets/repo-footer.svg)](https://maintainedby.lossless.com)

View File

@ -1,12 +1,17 @@
// tslint:disable-next-line:no-implicit-dependencies
import { expect, tap } from 'tapbundle';
import { expect, tap } from '@pushrocks/tapbundle';
import * as smartuniverse from '../ts/index';
import { Observable } from 'rxjs';
let testUniverse: smartuniverse.Universe;
let testUniverseClient: smartuniverse.UniverseClient;
let testMessageObservable: Observable<smartuniverse.UniverseMessage>;
let testClientUniverse: smartuniverse.ClientUniverse;
let testClientChannel: smartuniverse.ClientUniverseChannel;
const testChannelData = {
channelName: 'awesomeTestChannel',
channelPass: 'awesomeChannelPAss'
};
tap.test('first test', async () => {
testUniverse = new smartuniverse.Universe({
@ -15,31 +20,47 @@ tap.test('first test', async () => {
});
tap.test('add a message to the SmartUniverse', async () => {
await testUniverse.initServer(8765);
await testUniverse.start(8765);
});
// testing message handling
tap.test('create smartuniverse client', async () => {
testUniverseClient = new smartuniverse.UniverseClient({
testClientUniverse = new smartuniverse.ClientUniverse({
serverAddress: 'http://localhost:8765'
});
expect(testUniverseClient).to.be.instanceof(smartuniverse.UniverseClient);
expect(testClientUniverse).to.be.instanceof(smartuniverse.ClientUniverse);
});
tap.test('should send a message correctly', async () => {
await testUniverseClient.sendMessage('greeting', {
anyBool: true
});
tap.test('should add a channel to the universe', async () => {
await testUniverse.addChannel(
testChannelData.channelName,
testChannelData.channelPass
);
});
tap.test('should add the same channel to the client universe in the same way', async () => {
await testClientUniverse.addChannel(
testChannelData.channelName,
testChannelData.channelPass
);
});
tap.test('should get a observable correctly', async () => {
testMessageObservable = testUniverseClient.getMessageObservable();
testClientChannel = await testClientUniverse.getChannel(testChannelData.channelName);
expect(testClientChannel).to.be.instanceof(smartuniverse.ClientUniverseChannel);
});
tap.test('should send a message correctly', async () => {
await testClientUniverse.sendMessage({
messageText: 'hello',
targetChannelName: testChannelData.channelName
});
});
tap.test('should receive a message correctly', async () => {});
tap.test('should disconnect the client correctly', async () => {
testUniverseClient.close();
testClientUniverse.close();
});
tap.test('should end the server correctly', async tools => {

View File

@ -1,3 +1,11 @@
// Client classes
export * from './smartuniverse.classes.clientuniverse';
export * from './smartuniverse.classes.clientuniversechannel';
// Server classes
export * from './smartuniverse.classes.universe';
export * from './smartuniverse.classes.universeclient';
export * from './smartuniverse.classes.universecache';
export * from './smartuniverse.classes.universechannel';
export * from './smartuniverse.classes.universemessage';
export * from './interfaces';

View File

@ -0,0 +1,15 @@
export interface IServerGetMessagesRequestBody {
channel: string;
topic?: string;
youngerThan: number;
}
/**
* the interface for a standard request
*/
export interface IServerPutMessageRequestBody {
channel: string;
passphrase: string;
message: string;
payload: any;
}

3
ts/interfaces/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './http.interfaces';
export * from './universechannel.interfaces';
export * from './universemessage.interfaces';

View File

@ -0,0 +1 @@
export interface IUniverseChannel {}

View File

@ -0,0 +1,15 @@
export interface IMessageCreator {
messageText: string;
payload?: string | number | any;
payloadStringType?: 'Buffer' | 'string' | 'object';
targetChannelName: string;
}
export interface IUniverseMessage extends IMessageCreator {
id: string;
/**
* time of creation
*/
timestamp: number;
passphrase: string;
}

View File

@ -0,0 +1,103 @@
import * as plugins from './smartuniverse.plugins';
import { Objectmap } from '@pushrocks/lik';
import { Observable } from 'rxjs';
import { Smartsocket, SmartsocketClient } from '@pushrocks/smartsocket';
import * as url from 'url';
import * as interfaces from './interfaces';
import {
ClientUniverseChannel,
UniverseMessage
} from './';
export interface IClientOptions {
serverAddress: string;
}
/**
* this class is for client side only!!!
* allows connecting to a universe server
*/
export class ClientUniverse {
public options;
public socketClient: plugins.smartsocket.SmartsocketClient;
public observableIntake: plugins.smartrx.ObservableIntake<UniverseMessage>;
public channelCache = new Objectmap<ClientUniverseChannel>();
constructor(optionsArg: IClientOptions) {
this.options = optionsArg;
}
/**
* adds a channel to the channelcache
* TODO: verify channel before adding it to the channel cache
*/
public async addChannel (channelNameArg: string, passphraseArg: string) {
const existingChannel = this.getChannel(channelNameArg);
if (existingChannel) {
throw new Error('channel exists');
}
// lets create the channel
ClientUniverseChannel.createClientUniverseChannel(
this,
channelNameArg,
passphraseArg
);
}
/**
* gets a channel from the channelcache
* @param channelName
* @param passphraseArg
*/
public async getChannel(channelName: string): Promise<ClientUniverseChannel> {
await this.checkConnection();
const clientUniverseChannel = this.channelCache.find(channel => {
return channel.name === channelName;
})
return clientUniverseChannel;
}
public async sendMessage(messageArg: interfaces.IMessageCreator) {
const requestBody: interfaces.IUniverseMessage = {
id: plugins.smartunique.shortId(),
timestamp: Date.now(),
passphrase: (await this.getChannel(messageArg.targetChannelName)).passphrase,
...messageArg,
};
const requestBodyString = JSON.stringify(requestBody);
// TODO: User websocket connection if available
const response = await plugins.smartrequest.postJson(`${this.options.serverAddress}/sendmessage` , {
requestBody: requestBodyString
});
}
public close() {
this.socketClient.disconnect();
}
/**
* checks the connection towards a universe server
* since password validation is done through other means, a connection should always be possible
*/
private async checkConnection(): Promise<void> {
if (!this.socketClient && !this.observableIntake) {
const parsedURL = url.parse(this.options.serverAddress);
this.socketClient = new SmartsocketClient({
alias: process.env.SOCKET_ALIAS || 'someclient',
password: 'UniverseClient',
port: parseInt(parsedURL.port, 10),
role: 'UniverseClient',
url: parsedURL.hostname
});
this.observableIntake = new plugins.smartrx.ObservableIntake();
this.socketClient.connect();
}
}
}

View File

@ -0,0 +1,51 @@
import * as plugins from './smartuniverse.plugins';
import * as interfaces from './interfaces';
import { ClientUniverse } from './';
export class ClientUniverseChannel implements interfaces.IUniverseChannel {
// ======
// STATIC
// ======
/**
* creates a channel and adds it to the cache of clientUniverseArg
* @param clientUniverseArg
* @param channelNameArg
* @param passphraseArg
*/
public static async createClientUniverseChannel(
clientUniverseArg: ClientUniverse,
channelNameArg: string,
passphraseArg: string
): Promise<ClientUniverseChannel> {
const clientChannel = new ClientUniverseChannel(clientUniverseArg, channelNameArg, passphraseArg);
clientUniverseArg.channelCache.add(clientChannel);
await clientChannel.subscribe();
return clientChannel;
}
// ========
// INSTANCE
// ========
// properties
public name: string;
public passphrase: string;
// refs
public clientUniverse: ClientUniverse;
constructor(clientUniverseArg: ClientUniverse, nameArg: string, passphraseArg: string) {
this.clientUniverse = clientUniverseArg;
this.name = nameArg;
this.passphrase = passphraseArg;
}
/**
* subscribes to a channel
* tells the universe about this instances interest into a channel
*/
public async subscribe() {
this.clientUniverse.socketClient;
}
}

View File

@ -0,0 +1,32 @@
import * as plugins from './smartuniverse.plugins';
import * as interfaces from './interfaces';
export class ClientUniverseMessage implements interfaces.IUniverseMessage {
// ======
// STATIC
// ======
public static createMessageFromPayload(messageDescriptor: interfaces.IUniverseMessage) {
};
// ========
// INSTANCE
// ========
public id: string;
public timestamp: number;
public smartTimestamp: plugins.smarttime.TimeStamp;
public messageText: string;
public passphrase: string;
public payload: any;
public payloadStringType;
public targetChannelName: string;
constructor(messageArg, payloadArg) {}
getAsJsonForPayload () {
}
}

View File

@ -1,45 +1,38 @@
import * as plugins from './smartuniverse.plugins';
import { Handler, Route, Server } from 'smartexpress';
import { UniverseChannel } from './smartuniverse.classes.universechannel';
import { UniverseMessage } from './smartuniverse.classes.universemessage';
import { UniverseStore } from './smartuniverse.classes.universestore';
import { Handler, Route, Server } from '@pushrocks/smartexpress';
import { UniverseCache, UniverseChannel, UniverseMessage } from './';
import * as paths from './smartuniverse.paths';
import * as interfaces from './interfaces';
export interface ISmartUniverseConstructorOptions {
messageExpiryInMilliseconds: number;
}
export interface IServerGetMessagesRequestBody {
channel: string;
topic?: string;
youngerThan: number;
}
/**
* the interface for a standard request
*/
export interface IServerPutMessageRequestBody {
channel: string;
passphrase: string;
message: string;
payload: any;
}
/**
* main class that setsup a Universe
*/
export class Universe {
// subinstances
public universeStore: UniverseStore;
public universeCache: UniverseCache;
// options
private options: ISmartUniverseConstructorOptions;
// Store version handling
/**
* stores the version of the universe server running
* this is done since the version is exposed through the api and multiple fs actions are avoided this way.
*/
private universeVersionStore: string;
private get universeVersion() {
/**
* get the currently running version of smartuniverse
*/
public get universeVersion() {
if (this.universeVersionStore) {
return this.universeVersionStore;
} else {
@ -49,65 +42,83 @@ export class Universe {
}
}
/**
* the smartexpress server used
*/
private smartexpressServer: plugins.smartexpress.Server;
/**
* the smartsocket used
*/
private smartsocket: plugins.smartsocket.Smartsocket;
constructor(optionsArg: ISmartUniverseConstructorOptions) {
this.options = optionsArg;
this.universeStore = new UniverseStore(this.options.messageExpiryInMilliseconds);
this.universeCache = new UniverseCache(this.options.messageExpiryInMilliseconds);
}
/**
* adds a channel to the Universe
*/
public async addChannel(nameArg: string, passphraseArg: string) {
const newChannel = UniverseChannel.createChannel(this.universeCache, nameArg, passphraseArg);
}
/**
* initiates a server
*/
public async initServer(portArg: number | string) {
public async start(portArg: number | string) {
// lets create the base smartexpress server
this.smartexpressServer = new plugins.smartexpress.Server({
cors: true,
defaultAnswer: `smartuniverse server ${this.universeVersion}`,
defaultAnswer: async () => {
return `smartuniverse server ${this.universeVersion}`;
},
forceSsl: false,
port: portArg
});
// message handling
// adds messages
const addMessageHandler = new Handler('PUT', request => {
const requestBody: IServerPutMessageRequestBody = request.body;
const message = new UniverseMessage(requestBody.message, requestBody.channel, requestBody.passphrase, requestBody.payload);
this.universeStore.addMessage(message);
console.log(requestBody);
return true;
});
// gets messages
const readMessageHandler = new Handler('GET', request => {
const done = plugins.smartq.defer<UniverseMessage[]>();
const requestBody = request.body;
const messageObservable = this.universeStore.readMessagesYoungerThan(requestBody.since);
messageObservable.toArray().subscribe(universeMessageArrayArg => {
done.resolve(universeMessageArrayArg);
});
return done.promise;
});
// create new Route for messages
const messageRoute = new Route(this.smartexpressServer, 'message');
messageRoute.addHandler(addMessageHandler);
messageRoute.addHandler(readMessageHandler);
const leaderElectionRoute = new Route(this.smartexpressServer, 'leadelection');
// TODO: implement Handlers for leader election
// lets create the http request route
this.smartexpressServer.addRoute('/sendmessage', new Handler('POST', async (req, res) => {
const universeMessageInstance = new UniverseMessage(req.body);
this.universeCache.addMessage(universeMessageInstance);
}));
// add websocket upgrade
this.smartsocket = new plugins.smartsocket.Smartsocket({
port: 12345 // fix this within smartsocket
});
this.smartsocket.setExternalServer('express', this.smartexpressServer as any); // should work with express as well
// add a role for the clients
const ClientRole = new plugins.smartsocket.SocketRole({
name: 'clientuniverse',
passwordHash: 'clientuniverse' // authentication happens on another level
});
// add the role to smartsocket
this.smartsocket.addSocketRoles([ClientRole]);
const SubscriptionSocketFunction = new plugins.smartsocket.SocketFunction({
allowedRoles: [ClientRole],
funcName: 'channelSubscription',
funcDef: () => {
console.log('a client connected');
} // TODO: implement an action upon connection of clients
});
// add smartsocket to the running smartexpress app
this.smartsocket.setExternalServer('express', this.smartexpressServer as any);
// start the socket
this.smartsocket.start();
// start the smartexpress instance
await this.smartexpressServer.start();
}
/**
* stop everything
*/
public async stopServer() {
await this.smartsocket.stop();
await this.smartexpressServer.stop();

View File

@ -0,0 +1,63 @@
import * as plugins from './smartuniverse.plugins';
import { UniverseChannel } from './smartuniverse.classes.universechannel';
import { UniverseMessage } from './smartuniverse.classes.universemessage';
import { Objectmap } from '@pushrocks/lik';
import { Observable, from } from 'rxjs';
import { filter } from 'rxjs/operators';
import { rxjs } from '@pushrocks/smartrx';
/**
* universe store handles the creation, storage and retrieval of messages.
*/
export class UniverseCache {
// ========
// INSTANCE
// ========
public standardMessageExpiry: number;
public destructionTime: number = 60000;
/**
* stores messages for this instance
*/
public messageMap = new Objectmap<UniverseMessage>();
/**
* stores the channels that are available within the universe
*/
public channelMap = new Objectmap<UniverseChannel>();
/**
* allows messages to be processed in a blacklist mode for further analysis
*/
public blackListChannel = new UniverseChannel(this, 'blacklist', 'nada');
constructor(standardMessageExpiryArg: number) {
this.standardMessageExpiry = standardMessageExpiryArg;
}
/**
* add a message to the UniverseCache
* @param messageArg
* @param attachedPayloadArg
*/
public async addMessage(messageArg: UniverseMessage) {
messageArg.setUniverseCache(this);
UniverseChannel.authorizeAMessageForAChannel(this, messageArg);
this.messageMap.add(messageArg);
}
/**
* Read a message from the UniverseCache
*/
public readMessagesYoungerThan(unixTimeArg?: number): Observable<UniverseMessage> {
const messageObservable = from(this.messageMap.getArray()).pipe(
filter(messageArg => {
return messageArg.smartTimestamp.isYoungerThanMilliSeconds(this.destructionTime);
})
);
return messageObservable;
}
}

View File

@ -1,61 +1,63 @@
import * as plugins from './smartuniverse.plugins';
import { Objectmap } from 'lik';
import { Objectmap } from '@pushrocks/lik';
import { UniverseCache } from './smartuniverse.classes.universecache';
import { UniverseMessage } from './smartuniverse.classes.universemessage';
/**
* enables messages to stay within a certain scope.
*/
export class UniverseChannel {
// ======
// STATIC
// ======
/**
* stores the channels that are available within the universe
*/
public static channelStore = new Objectmap<UniverseChannel>();
/**
* allows messages to be processed in a blacklist mode for further analysis
*/
public static blackListChannel = new UniverseChannel('blacklist', 'nada');
/**
* creates new channels
* @param channelArg the name of the topic
* @param passphraseArg the secret thats used for a certain topic.
*/
public static createChannel (channelNameArg: string, passphraseArg: string) {
const newChannel = new UniverseChannel(channelNameArg, passphraseArg);
public static createChannel(
universeCacheArg: UniverseCache,
channelNameArg: string,
passphraseArg: string
) {
const newChannel = new UniverseChannel(universeCacheArg, channelNameArg, passphraseArg);
universeCacheArg.channelMap.add(newChannel);
return newChannel;
};
}
/**
* returns boolean wether certain channel exists
*/
public static async doesChannelExists (channelNameArg: string) {
const channel = this.channelStore.find(channelArg => {
public static async doesChannelExists(universeCacheArg: UniverseCache, channelNameArg: string) {
const channel = universeCacheArg.channelMap.find(channelArg => {
return channelArg.name === channelNameArg;
});
if(channel) {
if (channel) {
return true;
} else {
return false;
}
}
public static authorizeForChannel (channelNameArg: string, passphraseArg: string) {
const foundChannel = this.channelStore.find(universeChannel => {
const result = universeChannel.authenticate(channelNameArg, passphraseArg);
return result;
public static authorizeAMessageForAChannel(
universeCacheArg: UniverseCache,
universeMessageArg: UniverseMessage
) {
const foundChannel = universeCacheArg.channelMap.find(universeChannel => {
const result = universeChannel.authenticate(universeMessageArg);
return result;
});
if(foundChannel) {
if (foundChannel) {
universeMessageArg.authenticated = true;
universeMessageArg.universeChannelList.add(foundChannel);
return foundChannel;
} else {
return this.blackListChannel;
universeMessageArg.authenticated = false;
universeMessageArg.universeChannelList.add(universeCacheArg.blackListChannel);
}
};
}
// ========
// INSTANCE
@ -64,22 +66,27 @@ export class UniverseChannel {
* the name of the channel
*/
public name: string;
public universeCacheInstance: UniverseCache;
/**
* the passphrase for the channel
*/
public passphrase: string;
constructor(channelNameArg: string, passphraseArg: string) {
constructor(universeCacheArg: UniverseCache, channelNameArg: string, passphraseArg: string) {
this.name = channelNameArg;
this.passphrase = passphraseArg;
UniverseChannel.channelStore.add(this);
}
/**
* authenticates a client on the server side
*/
public authenticate(channelNameArg: string, passphraseArg: string): boolean {
return (this.name === channelNameArg && this.passphrase === passphraseArg);
public authenticate(universeMessageArg: UniverseMessage): boolean {
return (
this.name === universeMessageArg.targetChannelName &&
this.passphrase === universeMessageArg.passphrase
);
}
public pushToClients(messageArg: UniverseMessage) {}
}

View File

@ -1,60 +0,0 @@
import * as plugins from './smartuniverse.plugins';
import { Observable } from 'rxjs';
import { Smartsocket, SmartsocketClient } from 'smartsocket';
import * as url from 'url';
import {
IServerGetMessagesRequestBody,
IServerPutMessageRequestBody
} from './smartuniverse.classes.universe';
import { UniverseMessage } from './smartuniverse.classes.universemessage';
export interface IClientOptions {
serverAddress: string;
}
/**
* this class is for client side only!!!
* allows connecting to a universe server
*/
export class UniverseClient {
public options;
private socketClient: plugins.smartsocket.SmartsocketClient;
private observableIntake: plugins.smartrx.ObservableIntake<UniverseMessage>;
constructor(optionsArg: IClientOptions) {
this.options = optionsArg;
}
public async sendMessage(messageArg, payloadArg) {
const requestBody = {
message: messageArg,
payload: payloadArg
};
// TODO: User websocket connection if available
await plugins.smartrequest.post(this.options.serverAddress, {
requestBody
});
}
public getMessageObservable() {
if (!this.socketClient && !this.observableIntake) {
const parsedURL = url.parse(this.options.serverAddress);
this.socketClient = new SmartsocketClient({
alias: process.env.SOCKET_ALIAS || 'someclient',
password: 'UniverseClient',
port: parseInt(parsedURL.port, 10),
role: 'UniverseClient',
url: parsedURL.hostname
});
this.observableIntake = new plugins.smartrx.ObservableIntake();
this.socketClient.connect();
}
return this.observableIntake.observable;
}
public close() {
this.socketClient.disconnect();
}
}

View File

@ -1,45 +1,49 @@
import * as plugins from './smartuniverse.plugins';
import * as interfaces from './interfaces';
import { Timer, TimeStamp } from 'smarttime';
import { UniverseChannel } from './smartuniverse.classes.universechannel';
import { UniverseStore } from './smartuniverse.classes.universestore';
import { Objectmap } from '@pushrocks/lik';
import { Timer, TimeStamp } from '@pushrocks/smarttime';
import { Universe } from './smartuniverse.classes.universe';
import { UniverseChannel } from './smartuniverse.classes.universechannel';
import { UniverseCache } from './smartuniverse.classes.universecache';
import { IUniverseMessage } from './interfaces';
/**
* represents a message within a universe
* acts as a container to save message states like authentication status
*/
export class UniverseMessage {
/**
* public and unique id
* numeric ascending
* adheres to time in milliseconds
* avoids duplications though
*/
public id: number;
export class UniverseMessage implements interfaces.IUniverseMessage {
public id: string;
public timestamp: number;
public smartTimestamp: TimeStamp;
public messageText: string;
public passphrase: string;
public payload: any;
public payloadStringType;
public targetChannelName: string;
/**
* the universe store the message is attached to
* the UniverseCache the message is attached to
*/
public universeStore: UniverseStore;
public universeCache: UniverseCache;
/**
* enables unprotected grouping of messages for efficiency purposes.
*/
public universeChannel: UniverseChannel;
public universeChannelList = new Objectmap<UniverseChannel>();
/**
* time of creation
* wether the message is authenticated
*/
public timestamp: TimeStamp;
public authenticated: boolean = null;
/**
* the actual message
* a destruction timer for this message
*/
public message: string;
/**
* any attached payloads. Can be of binary format.
*/
public attachedPayload: any;
public destructionTimer: Timer; // a timer to take care of message destruction
/**
@ -47,16 +51,18 @@ export class UniverseMessage {
* @param messageArg
* @param attachedPayloadArg
*/
constructor(messageArg: string, channelNameArg: string, passphraseArg: string, attachedPayloadArg: any) {
this.timestamp = new TimeStamp();
this.message = messageArg;
this.universeChannel = UniverseChannel.authorizeForChannel(channelNameArg, passphraseArg);
this.attachedPayload = attachedPayloadArg;
constructor(messageDescriptor: IUniverseMessage) {
this.smartTimestamp = new TimeStamp(this.timestamp);
this.messageText = messageDescriptor.messageText;
this.targetChannelName = messageDescriptor.targetChannelName;
this.passphrase = messageDescriptor.passphrase;
this.payload = messageDescriptor.payload;
// prevent memory issues
this.fallBackDestruction();
}
public setUniverseStore(universeStoreArg: UniverseStore) {
this.universeStore = universeStoreArg;
public setUniverseCache(universeCacheArg: UniverseCache) {
this.universeCache = universeCacheArg;
}
public setDestructionTimer(selfdestructAfterArg: number) {
@ -64,15 +70,22 @@ export class UniverseMessage {
this.destructionTimer = new Timer(selfdestructAfterArg);
this.destructionTimer.start();
// set up self destruction by removing this from the parent messageStore
// set up self destruction by removing this from the parent messageCache
this.destructionTimer.completed.then(async () => {
this.universeStore.messageStore.remove(this);
this.universeCache.messageMap.remove(this);
});
} else {
this.fallBackDestruction();
}
}
/**
* handles bad messages for further analysis
*/
public handleAsBadMessage() {
console.log('received a bad message');
}
/**
* prevents memory leaks if channels have no default
*/

View File

@ -1,43 +0,0 @@
import * as plugins from './smartuniverse.plugins';
import { UniverseMessage } from './smartuniverse.classes.universemessage';
import { Objectmap } from 'lik';
import { Observable } from 'rxjs';
import { rxjs } from 'smartrx';
/**
* universe store handles the creation, storage and retrieval of messages.
*/
export class UniverseStore {
public standardMessageExpiry: number;
public destructionTime: number = 60000;
public messageStore = new Objectmap<UniverseMessage>();
private lastId: number = 0; // stores the last id
constructor(standardMessageExpiryArg: number) {
this.standardMessageExpiry = standardMessageExpiryArg;
}
/**
* add a message to the UniverseStore
* @param messageArg
* @param attachedPayloadArg
*/
public addMessage(messageArg: UniverseMessage) {
this.messageStore.add(messageArg);
}
/**
* Read a message from the UniverseStore
*/
public readMessagesYoungerThan(unixTimeArg?: number): Observable<UniverseMessage> {
const messageObservable = rxjs.Observable.from(this.messageStore.getArray()).filter(
messageArg => {
return messageArg.timestamp.isYoungerThanMilliSeconds(this.destructionTime);
}
);
return messageObservable;
}
}

View File

@ -1,14 +0,0 @@
import * as plugins from './smartuniverse.plugins';
import { Universe } from './index';
process.env.CLI = 'true';
const universeCli = new plugins.smartcli.Smartcli();
universeCli.standardTask().subscribe(async argvArg => {
const standardUniverse = new Universe({
messageExpiryInMilliseconds: 60000
});
await standardUniverse.initServer(8765);
});

View File

@ -1,27 +1,31 @@
import * as smartcli from '@pushrocks/smartcli';
import * as lik from 'lik';
import * as nodehash from 'nodehash';
// node native
import * as path from 'path';
import * as smartdelay from 'smartdelay';
import * as smartexpress from 'smartexpress';
import * as smartfile from 'smartfile';
import * as smartq from 'smartq';
import * as smartrequest from 'smartrequest';
import * as smartrx from 'smartrx';
import * as smartsocket from 'smartsocket';
import * as smarttime from 'smarttime';
export { path };
// pushrocks scope
import * as lik from '@pushrocks/lik';
import * as smarthash from '@pushrocks/smarthash';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartexpress from '@pushrocks/smartexpress';
import * as smartfile from '@pushrocks/smartfile';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartrequest from '@pushrocks/smartrequest';
import * as smartrx from '@pushrocks/smartrx';
import * as smartsocket from '@pushrocks/smartsocket';
import * as smarttime from '@pushrocks/smarttime';
import * as smartunique from '@pushrocks/smartunique';
export {
lik,
nodehash,
path,
smartcli,
smarthash,
smartdelay,
smartexpress,
smartfile,
smartq,
smartpromise,
smartrx,
smartrequest,
smartsocket,
smarttime
smarttime,
smartunique
};

View File

@ -1,12 +1,17 @@
{
"extends": [
"tslint:latest",
"tslint-config-prettier"
],
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"semicolon": [
true,
"always"
]
}
"semicolon": [true, "always"],
"no-console": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"member-ordering": {
"options":{
"order": [
"static-method"
]
}
}
},
"defaultSeverity": "warning"
}