Compare commits

...

86 Commits

Author SHA1 Message Date
564988185d 1.2.0 2020-12-26 18:06:23 +00:00
8442f3570f feat(SmartsocketClient): socket client can now be stopped with .stop() addiditionally to .reconnect(), which will still try to re 2020-12-26 18:06:22 +00:00
196357c878 1.1.71 2020-12-26 17:43:20 +00:00
bc187b7e41 fix(core): update 2020-12-26 17:43:19 +00:00
0e54bf889f 1.1.70 2020-12-22 00:19:00 +00:00
4c211bc82e fix(test): use @pushrocks/isohash instead of @pushrocks/smarthash in tests 2020-12-22 00:18:59 +00:00
a1be281670 1.1.69 2020-12-16 02:20:28 +00:00
1a44d2027c fix(core): update 2020-12-16 02:20:28 +00:00
4bf5456a1d 1.1.68 2020-12-16 01:38:58 +00:00
57c748657a fix(core): update 2020-12-16 01:38:57 +00:00
15df24bf68 1.1.67 2020-09-30 00:20:54 +00:00
ca7470eedf fix(core): update 2020-09-30 00:20:53 +00:00
38f017934c 1.1.66 2020-09-29 19:43:10 +00:00
1980824540 fix(core): update 2020-09-29 19:43:10 +00:00
09dec2071e 1.1.65 2020-09-29 19:42:38 +00:00
a1443deafe fix(core): update 2020-09-29 19:42:38 +00:00
9eac5ad336 1.1.64 2020-09-29 19:37:49 +00:00
cf607a79d5 fix(core): update 2020-09-29 19:37:49 +00:00
8426c976bf 1.1.63 2020-09-29 18:58:10 +00:00
1086065000 fix(core): update 2020-09-29 18:58:09 +00:00
72196ec383 1.1.62 2020-09-29 17:21:08 +00:00
c6ad490a6f fix(core): update 2020-09-29 17:21:08 +00:00
a40a83f0bf 1.1.61 2020-09-29 08:35:27 +00:00
82fa029d05 fix(core): update 2020-09-29 08:35:26 +00:00
0d76e7f577 1.1.60 2020-09-24 18:04:12 +00:00
57e6f058be fix(core): update 2020-09-24 18:04:11 +00:00
b0bf9d7c95 1.1.59 2020-09-24 18:03:02 +00:00
1fbc76ef30 fix(core): update 2020-09-24 18:03:01 +00:00
424d9cca91 1.1.58 2019-11-08 18:48:39 +01:00
d712270946 fix(core): update 2019-11-08 18:48:39 +01:00
f98c797ad8 1.1.57 2019-11-08 18:41:09 +01:00
a23e7349be fix(core): update 2019-11-08 18:41:08 +01:00
c390881a4e 1.1.56 2019-11-08 17:31:04 +01:00
5e64f4ca25 fix(core): update 2019-11-08 17:31:04 +01:00
765bc73197 1.1.55 2019-11-08 17:11:41 +01:00
105acaf97b fix(core): update 2019-11-08 17:11:41 +01:00
5139136af4 1.1.54 2019-11-07 00:26:47 +01:00
bcc4ce9a87 fix(core): update 2019-11-07 00:26:47 +01:00
20e7584eb9 1.1.53 2019-11-03 20:23:15 +01:00
59cbc343cc fix(core): update 2019-11-03 20:23:15 +01:00
75aa1f6f0d 1.1.52 2019-11-03 19:17:26 +01:00
3f073ab9b3 fix(core): update 2019-11-03 19:17:26 +01:00
08c1618ea8 1.1.51 2019-11-03 18:33:46 +01:00
eb181fa2f6 fix(core): update 2019-11-03 18:33:46 +01:00
c901ab75d3 1.1.50 2019-11-03 16:48:35 +01:00
075c59ed2c fix(core): update 2019-11-03 16:48:35 +01:00
8d358dd93d 1.1.49 2019-09-10 00:21:48 +02:00
e576d6058a fix(core): update 2019-09-10 00:21:47 +02:00
6546da2394 1.1.48 2019-09-09 23:58:32 +02:00
29597daba1 fix(core): update 2019-09-09 23:58:32 +02:00
c8b647d2fd 1.1.47 2019-09-09 16:20:43 +02:00
1fa4aca049 fix(core): update 2019-09-09 16:20:43 +02:00
2d4695de22 1.1.46 2019-09-01 18:07:53 +02:00
a3f4dcbc2a fix(core): update 2019-09-01 18:07:52 +02:00
d6e81288b4 1.1.45 2019-08-13 15:58:08 +02:00
c441d89596 fix(core): update 2019-08-13 15:58:08 +02:00
85b69e487a 1.1.44 2019-08-13 11:36:32 +02:00
0d94ed9345 fix(core): update 2019-08-13 11:36:31 +02:00
e188b18016 1.1.43 2019-08-13 09:37:23 +02:00
33c0fa3746 fix(core): update 2019-08-13 09:37:23 +02:00
52be1415ee 1.1.42 2019-08-12 22:46:57 +02:00
e75d5eabdb fix(core): update 2019-08-12 22:46:57 +02:00
fb99848df1 1.1.41 2019-08-12 22:45:58 +02:00
1bd39d0755 fix(core): update 2019-08-12 22:45:58 +02:00
d24c5f628c 1.1.40 2019-08-12 22:31:41 +02:00
33fbabdd72 fix(core): update 2019-08-12 22:31:40 +02:00
9d26fff986 1.1.39 2019-08-12 18:16:26 +02:00
41151e5436 fix(core): update 2019-08-12 18:16:25 +02:00
109c5197f3 1.1.38 2019-06-07 08:40:24 +02:00
c3d47c1f50 fix(core): update 2019-06-07 08:40:24 +02:00
1b45fb45e9 1.1.37 2019-06-06 23:11:03 +02:00
c22adc9b57 fix(core): update 2019-06-06 23:11:03 +02:00
35e87306dd 1.1.36 2019-05-02 11:46:36 +02:00
1b20aa5992 fix(core): update 2019-05-02 11:46:36 +02:00
a63f14da47 1.1.35 2019-04-26 17:35:15 +02:00
86cf94c5dc fix(core): update 2019-04-26 17:35:15 +02:00
0d39d6f237 1.1.34 2019-04-24 18:30:57 +02:00
1856ad5cfc fix(core): update 2019-04-24 18:30:56 +02:00
b5dab810e8 1.1.33 2019-04-24 17:57:17 +02:00
f145b85b1c fix(core): update 2019-04-24 17:57:16 +02:00
a8c647c5c7 1.1.32 2019-04-24 17:17:36 +02:00
22472e0adf fix(core): update 2019-04-24 17:17:36 +02:00
333ddb4d9c 1.1.31 2019-04-24 17:10:52 +02:00
126f020bf8 fix(core): update 2019-04-24 17:10:51 +02:00
7677b1bb48 1.1.30 2019-04-24 16:09:01 +02:00
114379c5b3 fix(core): update 2019-04-24 16:09:01 +02:00
27 changed files with 11055 additions and 1556 deletions

18
.gitignore vendored
View File

@ -1,4 +1,20 @@
node_modules/
.nogit/
# artifacts
coverage/
public/
pages/
# installs
node_modules/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_*/
# custom

View File

@ -1,16 +1,16 @@
# gitzone standard
image: hosttoday/ht-docker-node:npmci
# gitzone ci_default
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: "$CI_BUILD_STAGE"
- .npmci_cache/
key: '$CI_BUILD_STAGE'
stages:
- security
- test
- release
- metadata
- security
- test
- release
- metadata
# ====================
# security stage
@ -18,102 +18,114 @@ stages:
mirror:
stage: security
script:
- npmci git mirror
- npmci git mirror
only:
- tags
tags:
- docker
- notpriv
- lossless
- docker
- notpriv
snyk:
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install -g snyk
- npmci command npm install --ignore-scripts
- npmci command snyk test
- npmci command npm install --production --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=prod --production
tags:
- docker
- notpriv
- docker
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=dev
tags:
- docker
allow_failure: true
# ====================
# test stage
# ====================
testLTS:
testStable:
stage: test
script:
- npmci npm prepare
- npmci node install lts
- npmci npm install
- npmci npm test
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv
testSTABLE:
- docker
testBuild:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv
- docker
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
- npmci node install stable
- npmci npm publish
only:
- tags
- tags
tags:
- docker
- notpriv
- lossless
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
image: docker:stable
allow_failure: true
services:
- docker:stable-dind
only:
- tags
script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts:
paths: [codeclimate.json]
- npmci command npm install -g tslint typescript
- npmci npm prepare
- npmci npm install
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
tags:
- docker
- priv
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
- npmci trigger
only:
- tags
- tags
tags:
- docker
- notpriv
- lossless
- docker
- notpriv
pages:
image: hosttoday/ht-docker-node:npmci
stage: metadata
script:
- npmci command npm install -g typedoc typescript
- npmci node install lts
- npmci command npm install -g @gitzone/tsdoc
- npmci npm prepare
- npmci npm install
- npmci command typedoc --module "commonjs" --target "ES2016" --out public/ ts/
- npmci command tsdoc
tags:
- lossless
- docker
- notpriv
only:
@ -121,5 +133,5 @@ pages:
artifacts:
expire_in: 1 week
paths:
- public
- public
allow_failure: true

13
.snyk
View File

@ -1,4 +1,13 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.13.3
ignore: {}
version: v1.13.5
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JS-JSYAML-173999:
- '@pushrocks/smartexpress > @pushrocks/smartfile > js-yaml':
reason: None given
expires: '2019-05-24T15:16:11.291Z'
SNYK-JS-JSYAML-174129:
- '@pushrocks/smartexpress > @pushrocks/smartfile > js-yaml':
reason: None given
expires: '2019-05-24T15:16:11.291Z'
patch: {}

29
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,29 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "current file",
"type": "node",
"request": "launch",
"args": [
"${relativeFile}"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "test.ts",
"type": "node",
"request": "launch",
"args": [
"test/test.ts"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"schema": {
"type": "object",
"properties": {
"npmci": {
"type": "object",
"description": "settings for npmci"
},
"gitzone": {
"type": "object",
"description": "settings for gitzone",
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}
}
}
}
]
}

View File

@ -1,5 +1,6 @@
{
"gitzone": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"gitscope": "pushrocks",

11209
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
{
"name": "@pushrocks/smartsocket",
"version": "1.1.29",
"version": "1.2.0",
"description": "easy and secure websocket communication",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"scripts": {
"test": "tstest test/",
"build": "tsbuild"
"test": "(tstest test/)",
"build": "(tsbuild --web && tsbundle --from ./ts/index.ts --to dist_bundle/bundle.js)"
},
"repository": {
"type": "git",
@ -19,24 +19,45 @@
},
"homepage": "https://gitlab.com/pushrocks/smartsocket#README",
"dependencies": {
"@pushrocks/lik": "^3.0.5",
"@pushrocks/smartdelay": "^2.0.3",
"@pushrocks/smarthash": "^2.0.4",
"@pushrocks/smartlog": "^2.0.19",
"@pushrocks/smartpromise": "^3.0.2",
"@types/shortid": "0.0.29",
"@types/socket.io": "^2.1.2",
"@types/socket.io-client": "^1.4.32",
"shortid": "^2.2.14",
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0"
"@apiglobal/typedrequest-interfaces": "^1.0.15",
"@pushrocks/isohash": "^1.0.2",
"@pushrocks/isounique": "^1.0.4",
"@pushrocks/lik": "^4.0.20",
"@pushrocks/smartdelay": "^2.0.10",
"@pushrocks/smartenv": "^4.0.16",
"@pushrocks/smartexpress": "^3.0.99",
"@pushrocks/smartlog": "^2.0.39",
"@pushrocks/smartpromise": "^3.1.3",
"@pushrocks/smartrx": "^2.0.19",
"@pushrocks/smarttime": "^3.0.37",
"@types/socket.io": "^2.1.12",
"@types/socket.io-client": "^1.4.34",
"socket.io": "^3.0.4",
"socket.io-client": "^3.0.4"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.8",
"@gitzone/tsrun": "^1.2.5",
"@gitzone/tstest": "^1.0.20",
"@pushrocks/tapbundle": "^3.0.9",
"@types/node": "^11.13.7"
"@gitzone/tsbuild": "^2.1.25",
"@gitzone/tsrun": "^1.2.12",
"@gitzone/tstest": "^1.0.52",
"@pushrocks/tapbundle": "^3.2.9",
"@types/node": "^14.14.14",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0"
},
"private": false
"private": false,
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
],
"browserslist": [
"last 1 chrome versions"
]
}

View File

@ -8,13 +8,20 @@ easy and secure websocket communication
* [docs (typedoc)](https://pushrocks.gitlab.io/smartsocket/)
## Status for master
[![build status](https://gitlab.com/pushrocks/smartsocket/badges/master/build.svg)](https://gitlab.com/pushrocks/smartsocket/commits/master)
[![coverage report](https://gitlab.com/pushrocks/smartsocket/badges/master/coverage.svg)](https://gitlab.com/pushrocks/smartsocket/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/@pushrocks/smartsocket.svg)](https://www.npmjs.com/package/@pushrocks/smartsocket)
[![Known Vulnerabilities](https://snyk.io/test/npm/@pushrocks/smartsocket/badge.svg)](https://snyk.io/test/npm/@pushrocks/smartsocket)
[![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/)
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/pushrocks/smartsocket/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/pushrocks/smartsocket/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@pushrocks/smartsocket)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/pushrocks/smartsocket)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartsocket)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartsocket)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartsocket)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage
@ -30,7 +37,7 @@ import * as q from q; // q is a promise library
// The "Smartsocket" listens on a port and can receive new "SocketConnection" requests.
let mySmartsocket = new smartsocket.Smartsocket({
port: 3000 // the port smartsocket will listen on
port: 3000, // the port smartsocket will listen on
});
// optional:
@ -43,7 +50,7 @@ mySmartsocket.setServer(someExpressServer);
// "SocketFunction"s know which "SocketRole"s are allowed to execute them
let mySocketRole = new smartsocket.SocketRole({
name: 'someRoleName',
passwordHash: 'someHashedString'
passwordHash: 'someHashedString',
});
// A "SocketFunction" executes a referenced function and passes in any data of the corresponding "SocketRequest".
@ -51,10 +58,10 @@ let mySocketRole = new smartsocket.SocketRole({
// Any "SocketRequest" carries a unique identifier. If the referenced function's promise resolved any passed on argument will be returned to the requesting party
let testSocketFunction1 = new smartsocket.SocketFunction({
funcName: 'testSocketFunction1',
funcDef: data => {
funcDef: (data) => {
console.log('testSocketFunction1 executed successfully!');
},
allowedRoles: [mySocketRole] // all roles that have access to a specific function
allowedRoles: [mySocketRole], // all roles that have access to a specific function
});
// A "Smartsocket" exposes a .clientCall() that gets
@ -62,7 +69,7 @@ let testSocketFunction1 = new smartsocket.SocketFunction({
// 2. the data to pass in
// 3. And a target "SocketConnection" (there can be multiple connections at once)
// any unique id association is done internally
mySmartsocket.clientCall('restart', data, someTargetConnection).then(responseData => {});
mySmartsocket.clientCall('restart', data, someTargetConnection).then((responseData) => {});
```
#### Client side
@ -77,7 +84,7 @@ let testSmartsocketClient = new smartsocket.SmartsocketClient({
url: 'http://localhost',
password: 'testPassword',
alias: 'testClient1',
role: 'testRole1'
role: 'testRole1',
});
// You can .connect() and .disconnect() from a "Smartsocket"
@ -88,12 +95,12 @@ testSmartsocketClient.connect().then(() => {
// The client can also specify "SocketFunction"s. It can also specify "SocketRole"s in case a client connects to multiple servers at once
let testSocketFunction2 = new smartsocket.SocketFunction({
funcName: 'testSocketFunction2',
funcDef: data => {}, // the function to execute, has to return promise
allowedRoles: []
funcDef: (data) => {}, // the function to execute, has to return promise
allowedRoles: [],
});
// A "SmartsocketClient" can call functions on the serverside using .serverCall() analog to the "Smartsocket"'s .clientCall method.
mySmartsocketClient.serverCall('function', functionCallData).then(functionResponseData => {
mySmartsocketClient.serverCall('function', functionCallData).then((functionResponseData) => {
// the functionResponseData comes from the server... awesome, right?
});
```
@ -103,9 +110,13 @@ mySmartsocketClient.serverCall('function', functionCallData).then(functionRespon
> `data` is always a js object that you can design for your specific needs.
> It supports buffers for large binary data network exchange.
## Contribution
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
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)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://pushrocks.gitlab.io/assets/repo-footer.svg)](https://maintainedby.lossless.com)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)

1
test/common/test.ts Normal file
View File

@ -0,0 +1 @@
console.log('TODO');

117
test/test.expressserver.ts Normal file
View File

@ -0,0 +1,117 @@
// tslint:disable-next-line:no-implicit-dependencies
import { expect, tap } from '@pushrocks/tapbundle';
import * as isohash from '@pushrocks/isohash';
import * as smartexpress from '@pushrocks/smartexpress';
import smartsocket = require('../ts/index');
let testSmartsocket: smartsocket.Smartsocket;
let testSmartsocketClient: smartsocket.SmartsocketClient;
let testSocketRole1: smartsocket.SocketRole;
let testSocketFunction1: smartsocket.SocketFunction<any>;
let myseServer: smartexpress.Server;
const testConfig = {
port: 3005,
};
// class smartsocket
tap.test('should create a new smartsocket', async () => {
testSmartsocket = new smartsocket.Smartsocket({ port: testConfig.port });
expect(testSmartsocket).be.instanceOf(smartsocket.Smartsocket);
});
tap.test('Should accept an smartExpressServer as server', async () => {
myseServer = new smartexpress.Server({
cors: true,
forceSsl: false,
port: testConfig.port,
});
testSmartsocket.setExternalServer('smartexpress', myseServer);
await myseServer.start();
});
// class socketrole
tap.test('should add a socketrole', async () => {
testSocketRole1 = new smartsocket.SocketRole({
name: 'testRole1',
passwordHash: await isohash.sha256FromString('testPassword'),
});
testSmartsocket.addSocketRoles([testSocketRole1]);
});
// class SocketFunction
tap.test('should register a new Function', async () => {
testSocketFunction1 = new smartsocket.SocketFunction({
allowedRoles: [testSocketRole1],
funcDef: async (dataArg, socketConnectionArg) => {
return dataArg;
},
funcName: 'testFunction1',
});
testSmartsocket.addSocketFunction(testSocketFunction1);
console.log(testSmartsocket.socketFunctions);
});
tap.test('should start listening when .started is called', async () => {
await testSmartsocket.start();
});
// class SmartsocketClient
tap.test('should react to a new websocket connection from client', async () => {
testSmartsocketClient = new smartsocket.SmartsocketClient({
port: testConfig.port,
url: 'http://localhost',
password: 'testPassword',
alias: 'testClient1',
role: 'testRole1',
});
testSmartsocketClient.addSocketFunction(testSocketFunction1);
console.log(testSmartsocketClient.socketFunctions);
await testSmartsocketClient.connect();
});
tap.test('client should disconnect and reconnect', async (tools) => {
await testSmartsocketClient.disconnect();
await tools.delayFor(100);
await testSmartsocketClient.connect();
});
tap.test('2 clients should connect in parallel', async () => {
// TODO: implement parallel test
});
tap.test('should be able to make a functionCall from client to server', async () => {
const totalCycles = 20000;
let counter = 0;
let startTime = Date.now();
while (counter < totalCycles) {
const randomString = `hello ${Math.random()}`;
const response: any = await testSmartsocketClient.serverCall('testFunction1', {
value1: randomString,
});
expect(response.value1).to.equal(randomString);
if (counter % 100 === 0) {
console.log(
`processed 100 more messages in ${Date.now() - startTime}ms. ${
totalCycles - counter
} messages to go.`
);
startTime = Date.now();
}
counter++;
}
});
tap.test('should be able to make a functionCall from server to client', async () => {});
// terminate
tap.test('should close the server', async () => {
await testSmartsocket.stop();
await myseServer.stop();
});
tap.start();

View File

@ -1,19 +1,38 @@
// tslint:disable-next-line:no-implicit-dependencies
import { expect, tap } from '@pushrocks/tapbundle';
import * as nodehash from '@pushrocks/smarthash';
import * as smartq from '@pushrocks/smartpromise';
import socketIoClient = require('socket.io-client');
import smartsocket = require('../ts/index');
import * as isohash from '@pushrocks/isohash';
let testSmartsocket: smartsocket.Smartsocket;
let testSmartsocketClient: smartsocket.SmartsocketClient;
let testSocketConnection: smartsocket.SocketConnection;
let testSocketRole1: smartsocket.SocketRole;
let testSocketFunction1: smartsocket.SocketFunction;
let testSocketFunctionForServer: smartsocket.SocketFunction<any>;
let testSocketFunctionClient: smartsocket.SocketFunction<any>;
export interface IReqResClient {
method: 'testFunction1';
request: {
value1: string;
};
response: {
value1: string;
};
}
export interface IReqResServer {
method: 'testFunction2';
request: {
hi: string;
};
response: {
hi: string;
};
}
const testConfig = {
port: 3000
port: 3000,
};
// class smartsocket
@ -22,60 +41,53 @@ tap.test('should create a new smartsocket', async () => {
expect(testSmartsocket).be.instanceOf(smartsocket.Smartsocket);
});
tap.test('should start listening when .started is called', async () => {
await testSmartsocket.start();
});
// class socketrole
tap.test('should add a socketrole', async () => {
testSocketRole1 = new smartsocket.SocketRole({
name: 'testRole1',
passwordHash: nodehash.sha256FromStringSync('testPassword')
passwordHash: await isohash.sha256FromString('testPassword'),
});
testSmartsocket.addSocketRoles([testSocketRole1]);
});
// class SocketFunction
tap.test('should register a new Function', async () => {
testSocketFunction1 = new smartsocket.SocketFunction({
testSocketFunctionForServer = new smartsocket.SocketFunction({
allowedRoles: [testSocketRole1],
funcDef: async dataArg => {
funcDef: async (dataArg, socketConnectionArg) => {
return dataArg;
},
funcName: 'testFunction1'
funcName: 'testFunction1',
});
testSmartsocket.addSocketFunction(testSocketFunctionForServer);
testSocketFunctionClient = new smartsocket.SocketFunction({
allowedRoles: [],
funcDef: async (dataArg, socketConnectionArg) => {
return dataArg;
},
funcName: 'testFunction2',
});
testSmartsocket.addSocketFunction(testSocketFunctionForServer);
console.log(testSmartsocket.socketFunctions);
});
tap.test('should start listening when .started is called', async () => {
await testSmartsocket.start();
});
// class SmartsocketClient
tap.test('should react to a new websocket connection from client', async () => {
const done = smartq.defer();
testSmartsocketClient = new smartsocket.SmartsocketClient({
port: testConfig.port,
url: 'http://localhost',
password: 'testPassword',
alias: 'testClient1',
role: 'testRole1'
role: 'testRole1',
});
testSmartsocketClient.connect().then(() => {
done.resolve();
});
await done.promise;
});
tap.test('client should disconnect and reconnect', async () => {
let done = smartq.defer();
testSmartsocketClient
.disconnect()
.then(() => {
let done = smartq.defer();
setTimeout(() => {
testSmartsocketClient.connect().then(done.resolve);
}, 0);
return done.promise;
})
.then(() => {
done.resolve();
});
await done.promise;
testSmartsocketClient.addSocketFunction(testSocketFunctionClient);
console.log(testSmartsocketClient.socketFunctions);
await testSmartsocketClient.connect();
});
tap.test('2 clients should connect in parallel', async () => {
@ -83,37 +95,31 @@ tap.test('2 clients should connect in parallel', async () => {
});
tap.test('should be able to make a functionCall from client to server', async () => {
let done = smartq.defer();
testSmartsocketClient
.serverCall('testFunction1', {
value1: 'hello'
})
.then(dataArg => {
console.log(dataArg);
done.resolve();
});
await done.promise;
const response = await testSmartsocketClient.serverCall<IReqResClient>('testFunction1', {
value1: 'hello',
});
console.log(response);
expect(response.value1).to.equal('hello');
});
tap.test('should be able to make a functionCall from server to client', async () => {
let done = smartq.defer();
let targetSocket = (() => {
return smartsocket.allSocketConnections.find(socketConnectionArg => {
return socketConnectionArg.alias === 'testClient1';
});
})();
testSmartsocket
.clientCall(
'testFunction1',
{
value1: 'helloFromServer'
},
targetSocket
)
.then(dataArg => {
console.log(dataArg);
done.resolve();
});
const response = await testSmartsocket.clientCall<IReqResServer>(
'testFunction2',
{
hi: 'hi there from server',
},
testSmartsocket.socketConnections.find((socketConnection) => {
return true;
})
);
console.log(response);
expect(response.hi).to.equal('hi there from server');
});
tap.test('client should disconnect and reconnect', async (tools) => {
await testSmartsocketClient.disconnect();
await tools.delayFor(100);
await testSmartsocketClient.connect();
});
// terminate

View File

@ -1,5 +1,3 @@
import * as plugins from './smartsocket.plugins';
// export main classes
export * from './smartsocket.classes.smartsocket';
export * from './smartsocket.classes.smartsocketclient';

View File

@ -0,0 +1,10 @@
export interface IRequestAuthPayload {
serverShortId: string;
}
export type TConnectionStatus =
| 'new'
| 'connecting'
| 'connected'
| 'disconnecting'
| 'disconnected';

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

@ -0,0 +1 @@
export * from './connection';

View File

@ -1,26 +1,35 @@
import * as plugins from './smartsocket.plugins';
import * as helpers from './smartsocket.helpers';
import * as pluginsTyped from './smartsocket.pluginstyped';
// classes
import { Objectmap } from '@pushrocks/lik';
import { SocketConnection } from './smartsocket.classes.socketconnection';
import { ISocketFunctionCall, SocketFunction } from './smartsocket.classes.socketfunction';
import {
ISocketFunctionCallDataRequest,
SocketFunction,
ISocketFunctionCallDataResponse,
} from './smartsocket.classes.socketfunction';
import { SocketRequest } from './smartsocket.classes.socketrequest';
import { SocketRole } from './smartsocket.classes.socketrole';
import { SocketServer } from './smartsocket.classes.socketserver';
// socket.io
import * as SocketIO from 'socket.io';
import { logger } from './smartsocket.logging';
export interface ISmartsocketConstructorOptions {
port?: number;
}
export class Smartsocket {
/**
* a unique id to detect server restarts
*/
public shortId = plugins.isounique.uni();
public smartenv = new plugins.smartenv.Smartenv();
public options: ISmartsocketConstructorOptions;
public io: SocketIO.Server;
public openSockets = new Objectmap<SocketConnection>();
public socketRoles = new Objectmap<SocketRole>();
public io: pluginsTyped.socketIo.Server;
public socketConnections = new plugins.lik.ObjectMap<SocketConnection>();
public socketRoles = new plugins.lik.ObjectMap<SocketRole>();
public socketFunctions = new plugins.lik.ObjectMap<SocketFunction<any>>();
public socketRequests = new plugins.lik.ObjectMap<SocketRequest<any>>();
private socketServer = new SocketServer(this);
@ -29,15 +38,18 @@ export class Smartsocket {
}
// tslint:disable-next-line:member-ordering
public setExternalServer = this.socketServer.setExternalServer;
public async setExternalServer(serverType: 'smartexpress', serverArg: any) {
await this.socketServer.setExternalServer(serverType, serverArg);
}
/**
* starts smartsocket
*/
public async start() {
this.io = plugins.socketIo(this.socketServer.getServerForSocketIo());
const socketIoModule = this.smartenv.getSafeNodeModule('socket.io');
this.io = socketIoModule(this.socketServer.getServerForSocketIo());
await this.socketServer.start();
this.io.on('connection', socketArg => {
this.io.on('connection', (socketArg) => {
this._handleSocketConnection(socketArg);
});
}
@ -47,14 +59,13 @@ export class Smartsocket {
*/
public async stop() {
await plugins.smartdelay.delayFor(1000);
this.openSockets.forEach((socketObjectArg: SocketConnection) => {
plugins.smartlog.defaultLogger.log(
'info',
`disconnect socket with >>alias ${socketObjectArg.alias}`
);
socketObjectArg.socket.disconnect();
this.socketConnections.forEach((socketObjectArg: SocketConnection) => {
if (socketObjectArg) {
logger.log('info', `disconnect socket with >>alias ${socketObjectArg.alias}`);
socketObjectArg.socket.disconnect();
}
});
this.openSockets.wipe();
this.socketConnections.wipe();
this.io.close();
// stop the corresponging server
@ -66,25 +77,22 @@ export class Smartsocket {
/**
* allows call to specific client.
*/
public async clientCall(
functionNameArg: string,
dataArg: any,
public async clientCall<T extends plugins.typedrequestInterfaces.ITypedRequest>(
functionNameArg: T['method'],
dataArg: T['request'],
targetSocketConnectionArg: SocketConnection
) {
const done = plugins.smartpromise.defer();
const socketRequest = new SocketRequest({
): Promise<T['response']> {
const socketRequest = new SocketRequest<T>(this, {
funcCallData: {
funcDataArg: dataArg,
funcName: functionNameArg
funcName: functionNameArg,
},
originSocketConnection: targetSocketConnectionArg,
shortId: plugins.shortid.generate(),
side: 'requesting'
shortId: plugins.isounique.uni(),
side: 'requesting',
});
socketRequest.dispatch().then((dataArg: ISocketFunctionCall) => {
done.resolve(dataArg.funcDataArg);
});
const result = await done.promise;
const response: ISocketFunctionCallDataResponse<T> = await socketRequest.dispatch();
const result = response.funcDataArg;
return result;
}
@ -98,27 +106,25 @@ export class Smartsocket {
return;
}
public addSocketFunction(socketFunction: SocketFunction<any>) {
this.socketFunctions.add(socketFunction);
}
/**
* the standard handler for new socket connections
*/
private _handleSocketConnection(socketArg) {
private async _handleSocketConnection(socketArg: pluginsTyped.socketIo.Socket) {
const socketConnection: SocketConnection = new SocketConnection({
alias: undefined,
authenticated: false,
role: undefined,
side: 'server',
smartsocketHost: this,
socket: socketArg
socket: socketArg,
});
plugins.smartlog.defaultLogger.log('info', 'Socket connected. Trying to authenticate...');
this.openSockets.add(socketConnection);
socketConnection
.authenticate()
.then(() => {
return socketConnection.listenToFunctionRequests();
})
.catch(err => {
console.log(err);
});
logger.log('info', 'Socket connected. Trying to authenticate...');
this.socketConnections.add(socketConnection);
await socketConnection.authenticate();
await socketConnection.listenToFunctionRequests();
}
}

View File

@ -1,8 +1,15 @@
import * as plugins from './smartsocket.plugins';
import * as pluginsTyped from './smartsocket.pluginstyped';
import * as interfaces from './interfaces';
import { SocketConnection } from './smartsocket.classes.socketconnection';
import { ISocketFunctionCall, SocketFunction } from './smartsocket.classes.socketfunction';
import {
ISocketFunctionCallDataRequest,
SocketFunction,
} from './smartsocket.classes.socketfunction';
import { ISocketRequestDataObject, SocketRequest } from './smartsocket.classes.socketrequest';
import { SocketRole } from './smartsocket.classes.socketrole';
import { logger } from './smartsocket.logging';
/**
* interface for class SmartsocketClient
@ -13,78 +20,193 @@ export interface ISmartsocketClientOptions {
alias: string; // an alias makes it easier to identify this client in a multo client environment
role: string;
password: string; // by setting a password access to functions can be limited
autoReconnect?: boolean;
}
export class SmartsocketClient {
alias: string;
role: string;
socketConnection: SocketConnection;
serverUrl: string;
serverPort: number;
serverPassword: string;
// a unique id
public shortId = plugins.isounique.uni();
// the shortId of the remote we connect to
public remoteShortId: string = null;
public alias: string;
public socketRole: SocketRole;
public socketConnection: SocketConnection;
public serverUrl: string;
public serverPort: number;
public autoReconnect: boolean;
// status handling
public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>();
public eventStatus: interfaces.TConnectionStatus = 'new';
public socketFunctions = new plugins.lik.ObjectMap<SocketFunction<any>>();
public socketRequests = new plugins.lik.ObjectMap<SocketRequest<any>>();
public socketRoles = new plugins.lik.ObjectMap<SocketRole>();
constructor(optionsArg: ISmartsocketClientOptions) {
this.alias = optionsArg.alias;
this.role = optionsArg.role;
this.serverUrl = optionsArg.url;
this.serverPort = optionsArg.port;
this.serverPassword = optionsArg.password;
this.socketRole = new SocketRole({
name: optionsArg.role,
passwordHash: optionsArg.password,
});
this.autoReconnect = optionsArg.autoReconnect;
}
public addSocketFunction(socketFunction: SocketFunction<any>) {
this.socketFunctions.add(socketFunction);
this.socketRole.allowedFunctions.add(socketFunction);
}
/**
* connect the client to the server
*/
connect() {
let done = plugins.smartpromise.defer();
plugins.smartlog.defaultLogger.log('info', 'trying to connect...');
let socketUrl = `${this.serverUrl}:${this.serverPort}`;
public async connect() {
const done = plugins.smartpromise.defer();
const smartenvInstance = new plugins.smartenv.Smartenv();
const socketIoClient = await smartenvInstance.getEnvAwareModule({
nodeModuleName: 'socket.io-client',
webUrlArg: 'https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js',
getFunction: () => {
return globalThis.io;
},
});
logger.log('info', 'trying to connect...');
const socketUrl = `${this.serverUrl}:${this.serverPort}`;
this.socketConnection = new SocketConnection({
alias: this.alias,
authenticated: false,
role: undefined,
role: this.socketRole,
side: 'client',
smartsocketHost: null,
socket: plugins.socketIoClient(socketUrl, { multiplex: false })
smartsocketHost: this,
socket: await socketIoClient.connect(socketUrl, {
multiplex: false,
reconnectionAttempts: 5,
}),
});
this.socketConnection.socket.on('requestAuth', () => {
console.log('server requested authentication');
this.socketConnection.socket.emit('dataAuth', {
role: this.role,
password: this.serverPassword,
alias: this.alias
});
this.socketConnection.socket.on('authenticated', () => {
console.log('client is authenticated');
this.socketConnection.authenticated = true;
this.socketConnection.listenToFunctionRequests();
done.resolve();
});
const timer = new plugins.smarttime.Timer(5000);
timer.start();
timer.completed.then(() => {
logger.log('warn', 'connection to server timed out.');
this.disconnect();
});
// authentication flow
this.socketConnection.socket.on(
'requestAuth',
(requestAuthPayload: interfaces.IRequestAuthPayload) => {
timer.reset();
logger.log('info', 'server requested authentication');
// lets register the authenticated event
this.socketConnection.socket.on('authenticated', () => {
this.remoteShortId = requestAuthPayload.serverShortId;
logger.log('info', 'client is authenticated');
this.socketConnection.authenticated = true;
this.socketConnection.listenToFunctionRequests();
done.resolve();
});
// lets register the forbidden event
this.socketConnection.socket.on('forbidden', async () => {
logger.log('warn', `disconnecting due to being forbidden to use the ressource`);
await this.disconnect();
});
// lets provide the actual auth data
this.socketConnection.socket.emit('dataAuth', {
role: this.socketRole.name,
password: this.socketRole.passwordHash,
alias: this.alias,
});
}
);
// handle connection
this.socketConnection.socket.on('connect', async () => {
this.updateStatus('connected');
});
// handle disconnection and errors
this.socketConnection.socket.on('disconnect', async () => {
await this.disconnect();
});
this.socketConnection.socket.on('reconnect_failed', async () => {
await this.disconnect();
});
this.socketConnection.socket.on('connect_error', async () => {
await this.disconnect();
});
return done.promise;
}
disconnect() {
let done = plugins.smartpromise.defer();
this.socketConnection.socket.disconnect();
this.socketConnection = undefined;
plugins.smartlog.defaultLogger.log('ok', 'disconnected!');
done.resolve();
return done.promise;
/**
* disconnect from the server
*/
public async disconnect() {
if (this.socketConnection) {
await this.socketConnection.disconnect();
this.socketConnection = undefined;
logger.log('ok', 'disconnected!');
}
logger.log('warn', `disconnected from server ${this.remoteShortId}`);
this.remoteShortId = null;
this.updateStatus('disconnected');
if (this.autoReconnect) {
this.tryDebouncedReconnect();
}
}
serverCall(functionNameArg: string, dataArg: any) {
let done = plugins.smartpromise.defer();
let socketRequest = new SocketRequest({
/**
* stops the client completely
*/
public async stop() {
this.autoReconnect = false;
await this.disconnect();
}
/**
* try a reconnection
*/
public async tryDebouncedReconnect() {
await plugins.smartdelay.delayForRandom(10000, 60000);
await this.connect();
}
/**
* dispatches a server call
* @param functionNameArg
* @param dataArg
*/
public async serverCall<T extends plugins.typedrequestInterfaces.ITypedRequest>(
functionNameArg: T['method'],
dataArg: T['request']
): Promise<T['response']> {
const done = plugins.smartpromise.defer();
const socketRequest = new SocketRequest<T>(this, {
side: 'requesting',
originSocketConnection: this.socketConnection,
shortId: plugins.shortid.generate(),
shortId: plugins.isounique.uni(),
funcCallData: {
funcName: functionNameArg,
funcDataArg: dataArg
}
funcDataArg: dataArg,
},
});
socketRequest.dispatch().then((dataArg: ISocketFunctionCall) => {
done.resolve(dataArg.funcDataArg);
});
return done.promise;
const response = await socketRequest.dispatch();
const result = response.funcDataArg;
return result;
}
private updateStatus(statusArg: interfaces.TConnectionStatus) {
if (this.eventStatus !== statusArg) {
this.eventSubject.next(statusArg);
}
this.eventStatus = statusArg;
}
}

View File

@ -1,20 +1,16 @@
import * as plugins from './smartsocket.plugins';
import * as helpers from './smartsocket.helpers';
import { Objectmap } from '@pushrocks/lik';
import * as interfaces from './interfaces';
// import classes
import { Smartsocket } from './smartsocket.classes.smartsocket';
import { SocketFunction } from './smartsocket.classes.socketfunction';
import {
SocketRequest,
ISocketRequestDataObject,
allSocketRequests
} from './smartsocket.classes.socketrequest';
import { SocketRequest, ISocketRequestDataObject } from './smartsocket.classes.socketrequest';
import { SocketRole } from './smartsocket.classes.socketrole';
// socket.io
import * as SocketIO from 'socket.io';
import { SmartsocketClient } from './smartsocket.classes.smartsocketclient';
import { logger } from './smartsocket.logging';
// export interfaces
@ -31,7 +27,7 @@ export interface ISocketConnectionConstructorOptions {
authenticated: boolean;
role: SocketRole;
side: TSocketConnectionSide;
smartsocketHost: Smartsocket;
smartsocketHost: Smartsocket | SmartsocketClient;
socket: SocketIO.Socket | SocketIOClient.Socket;
}
@ -45,34 +41,43 @@ export interface ISocketConnectionAuthenticationObject {
}
// export classes
export let allSocketConnections = new Objectmap<SocketConnection>();
export let allSocketConnections = new plugins.lik.ObjectMap<SocketConnection>();
/**
* class SocketConnection represents a websocket connection
*/
export class SocketConnection {
alias: string;
side: TSocketConnectionSide;
authenticated: boolean = false;
role: SocketRole;
smartsocketHost: Smartsocket;
socket: any; // SocketIO.Socket | SocketIOClient.Socket
public alias: string;
public side: TSocketConnectionSide;
public authenticated: boolean = false;
public role: SocketRole;
public smartsocketRef: Smartsocket | SmartsocketClient;
public socket: SocketIO.Socket | SocketIOClient.Socket;
public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>();
public eventStatus: interfaces.TConnectionStatus = 'new';
constructor(optionsArg: ISocketConnectionConstructorOptions) {
this.alias = optionsArg.alias;
this.authenticated = optionsArg.authenticated;
this.role = optionsArg.role;
this.side = optionsArg.side;
this.smartsocketHost = optionsArg.smartsocketHost;
this.smartsocketRef = optionsArg.smartsocketHost;
this.socket = optionsArg.socket;
// standard behaviour that is always true
allSocketConnections.add(this);
this.socket.on('disconnect', () => {
plugins.smartlog.defaultLogger.log(
// handle connection
this.socket.on('connect', async () => {
this.updateStatus('connected');
});
this.socket.on('disconnect', async () => {
logger.log(
'info',
`SocketConnection with >alias ${this.alias} on >side ${this.side} disconnected`
);
this.socket.disconnect();
await this.disconnect();
allSocketConnections.remove(this);
});
}
@ -82,32 +87,29 @@ export class SocketConnection {
/**
* authenticate the socket
*/
authenticate() {
let done = plugins.smartpromise.defer();
this.socket.on('dataAuth', (dataArg: ISocketConnectionAuthenticationObject) => {
plugins.smartlog.defaultLogger.log(
'info',
'received authentication data. now hashing and comparing...'
);
public authenticate() {
const done = plugins.smartpromise.defer();
this.socket.on('dataAuth', async (dataArg: ISocketConnectionAuthenticationObject) => {
logger.log('info', 'received authentication data. now hashing and comparing...');
this.socket.removeListener('dataAuth', () => {});
if (helpers.checkPasswordForRole(dataArg, this.smartsocketHost)) {
if (await SocketRole.checkPasswordForRole(dataArg, this.smartsocketRef)) {
// TODO: authenticate password
this.alias = dataArg.alias;
this.authenticated = true;
this.role = helpers.getSocketRoleByName(dataArg.role, this.smartsocketHost);
this.role = SocketRole.getSocketRoleByName(this.smartsocketRef, dataArg.role);
this.socket.emit('authenticated');
plugins.smartlog.defaultLogger.log(
'ok',
`socket with >>alias ${this.alias} >>role ${this.role} is authenticated!`
);
logger.log('ok', `socket with >>alias ${this.alias} >>role ${this.role} is authenticated!`);
done.resolve(this);
} else {
this.authenticated = false;
this.socket.disconnect();
await this.disconnect();
done.reject('not authenticated');
}
});
this.socket.emit('requestAuth');
const requestAuthPayload: interfaces.IRequestAuthPayload = {
serverShortId: this.smartsocketRef.shortId,
};
this.socket.emit('requestAuth', requestAuthPayload);
return done.promise;
}
@ -116,53 +118,58 @@ export class SocketConnection {
/**
* listen to function requests
*/
listenToFunctionRequests() {
let done = plugins.smartpromise.defer();
public listenToFunctionRequests() {
const done = plugins.smartpromise.defer();
if (this.authenticated) {
this.socket.on('function', (dataArg: ISocketRequestDataObject) => {
this.socket.on('function', (dataArg: ISocketRequestDataObject<any>) => {
// check if requested function is available to the socket's scope
plugins.smartlog.defaultLogger.log('info', 'function request received');
let referencedFunction: SocketFunction = this.role.allowedFunctions.find(
socketFunctionArg => {
// logger.log('info', 'function request received');
const referencedFunction: SocketFunction<any> = this.role.allowedFunctions.find(
(socketFunctionArg) => {
return socketFunctionArg.name === dataArg.funcCallData.funcName;
}
);
if (referencedFunction !== undefined) {
plugins.smartlog.defaultLogger.log('ok', 'function in access scope');
let localSocketRequest = new SocketRequest({
if (referencedFunction) {
// logger.log('ok', 'function in access scope');
const localSocketRequest = new SocketRequest(this.smartsocketRef, {
side: 'responding',
originSocketConnection: this,
shortId: dataArg.shortId,
funcCallData: dataArg.funcCallData
funcCallData: dataArg.funcCallData,
});
localSocketRequest.createResponse(); // takes care of creating response and sending it back
} else {
plugins.smartlog.defaultLogger.log(
'warn',
'function not existent or out of access scope'
);
logger.log('warn', 'function not existent or out of access scope');
}
});
this.socket.on('functionResponse', (dataArg: ISocketRequestDataObject) => {
plugins.smartlog.defaultLogger.log(
'info',
`received response for request with id ${dataArg.shortId}`
this.socket.on('functionResponse', (dataArg: ISocketRequestDataObject<any>) => {
// logger.log('info', `received response for request with id ${dataArg.shortId}`);
const targetSocketRequest = SocketRequest.getSocketRequestById(
this.smartsocketRef,
dataArg.shortId
);
let targetSocketRequest = helpers.getSocketRequestById(dataArg.shortId);
targetSocketRequest.handleResponse(dataArg);
});
plugins.smartlog.defaultLogger.log(
'info',
`now listening to function requests for ${this.alias}`
);
logger.log('info', `now listening to function requests for ${this.alias}`);
done.resolve(this);
} else {
let errMessage: 'socket needs to be authenticated first';
plugins.smartlog.defaultLogger.log('error', errMessage);
const errMessage = 'socket needs to be authenticated first';
logger.log('error', errMessage);
done.reject(errMessage);
}
return done.promise;
}
// sending ----------------------
// disconnecting ----------------------
public async disconnect() {
this.socket.disconnect(true);
this.updateStatus('disconnected');
}
private updateStatus(statusArg: interfaces.TConnectionStatus) {
if (this.eventStatus !== statusArg) {
this.eventSubject.next(statusArg);
}
this.eventStatus = statusArg;
}
}

View File

@ -1,78 +1,101 @@
import * as plugins from './smartsocket.plugins';
// import classes
import { Objectmap } from '@pushrocks/lik';
import { SocketRole } from './smartsocket.classes.socketrole';
import { SocketConnection } from './smartsocket.classes.socketconnection';
import { Smartsocket } from './smartsocket.classes.smartsocket';
import { SmartsocketClient } from './smartsocket.classes.smartsocketclient';
// export interfaces
/**
* interface of the contructor options of class SocketFunction
*/
export interface ISocketFunctionConstructorOptions {
funcName: string;
funcDef: any;
export interface ISocketFunctionConstructorOptions<
T extends plugins.typedrequestInterfaces.ITypedRequest
> {
funcName: T['method'];
funcDef: TFuncDef<T>;
allowedRoles: SocketRole[]; // all roles that are allowed to execute a SocketFunction
}
/**
* interface of the Socket Function call, in other words the object that routes a call to a function
*/
export interface ISocketFunctionCall {
funcName: string;
funcDataArg: any;
export interface ISocketFunctionCallDataRequest<
T extends plugins.typedrequestInterfaces.ITypedRequest
> {
funcName: T['method'];
funcDataArg: T['request'];
}
/**
* interface of the Socket Function call, in other words the object that routes a call to a function
*/
export interface ISocketFunctionCallDataResponse<
T extends plugins.typedrequestInterfaces.ITypedRequest
> {
funcName: T['method'];
funcDataArg: T['response'];
}
/**
* interface for function definition of SocketFunction
*/
export interface IFuncDef {
(dataArg: any): PromiseLike<any>;
}
// export objects
export let allSocketFunctions = new Objectmap<SocketFunction>();
export type TFuncDef<T extends plugins.typedrequestInterfaces.ITypedRequest> = (
dataArg: T['request'],
connectionArg: SocketConnection
) => PromiseLike<T['response']>;
// export classes
/**
* class that respresents a function that can be transparently called using a SocketConnection
*/
export class SocketFunction {
name: string;
funcDef: IFuncDef;
roles: SocketRole[];
export class SocketFunction<T extends plugins.typedrequestInterfaces.ITypedRequest> {
// STATIC
public static getSocketFunctionByName<Q extends plugins.typedrequestInterfaces.ITypedRequest>(
smartsocketRefArg: Smartsocket | SmartsocketClient,
functionNameArg: string
): SocketFunction<Q> {
return smartsocketRefArg.socketFunctions.find((socketFunctionArg) => {
return socketFunctionArg.name === functionNameArg;
});
}
// INSTANCE
public name: string;
public funcDef: TFuncDef<T>;
public roles: SocketRole[];
/**
* the constructor for SocketFunction
*/
constructor(optionsArg: ISocketFunctionConstructorOptions) {
constructor(optionsArg: ISocketFunctionConstructorOptions<T>) {
this.name = optionsArg.funcName;
this.funcDef = optionsArg.funcDef;
this.roles = optionsArg.allowedRoles;
for (let socketRoleArg of this.roles) {
for (const socketRoleArg of this.roles) {
this._notifyRole(socketRoleArg);
}
allSocketFunctions.add(this); // map instance with Objectmap
}
/**
* invokes the function of this SocketFunction
*/
invoke(dataArg: ISocketFunctionCall): Promise<any> {
let done = plugins.smartpromise.defer();
public async invoke(
dataArg: ISocketFunctionCallDataRequest<T>,
socketConnectionArg: SocketConnection
): Promise<ISocketFunctionCallDataResponse<T>> {
if (dataArg.funcName === this.name) {
this.funcDef(dataArg.funcDataArg).then((resultData: any) => {
let funcResponseData: ISocketFunctionCall = {
funcName: this.name,
funcDataArg: resultData
};
done.resolve(funcResponseData);
});
const funcResponseData: ISocketFunctionCallDataResponse<T> = {
funcName: this.name,
funcDataArg: await this.funcDef(dataArg.funcDataArg, socketConnectionArg),
};
return funcResponseData;
} else {
throw new Error("SocketFunction.name does not match the data argument's .name!");
}
return done.promise;
}
/**

View File

@ -1,13 +1,17 @@
import * as plugins from './smartsocket.plugins';
import * as helpers from './smartsocket.helpers';
// import interfaces
import { ISocketFunctionCall } from './smartsocket.classes.socketfunction';
import {
SocketFunction,
ISocketFunctionCallDataRequest,
ISocketFunctionCallDataResponse,
} from './smartsocket.classes.socketfunction';
// import classes
import { Objectmap } from '@pushrocks/lik';
import { SocketFunction } from './smartsocket.classes.socketfunction';
import { SocketConnection } from './smartsocket.classes.socketconnection';
import { logger } from './smartsocket.logging';
import { Smartsocket } from './smartsocket.classes.smartsocket';
import { SmartsocketClient } from './smartsocket.classes.smartsocketclient';
// export interfaces
export type TSocketRequestStatus = 'new' | 'pending' | 'finished';
@ -16,39 +20,56 @@ export type TSocketRequestSide = 'requesting' | 'responding';
/**
* interface of constructor of class SocketRequest
*/
export interface SocketRequestConstructorOptions {
export interface ISocketRequestConstructorOptions<
T extends plugins.typedrequestInterfaces.ITypedRequest
> {
side: TSocketRequestSide;
originSocketConnection: SocketConnection;
shortId: string;
funcCallData?: ISocketFunctionCall;
funcCallData?: ISocketFunctionCallDataRequest<T>;
}
/**
* request object that is sent initially and may or may not receive a response
*/
export interface ISocketRequestDataObject {
funcCallData: ISocketFunctionCall;
export interface ISocketRequestDataObject<T extends plugins.typedrequestInterfaces.ITypedRequest> {
funcCallData: ISocketFunctionCallDataRequest<T> | ISocketFunctionCallDataResponse<T>;
shortId: string;
responseTimeout?: number;
}
// export objects
export let allSocketRequests = new Objectmap<SocketRequest>();
// export classes
export class SocketRequest {
status: TSocketRequestStatus = 'new';
side: TSocketRequestSide;
shortid: string;
originSocketConnection: SocketConnection;
funcCallData: ISocketFunctionCall;
done = plugins.smartpromise.defer();
constructor(optionsArg: SocketRequestConstructorOptions) {
export class SocketRequest<T extends plugins.typedrequestInterfaces.ITypedRequest> {
// STATIC
public static getSocketRequestById(
smartsocketRef: Smartsocket | SmartsocketClient,
shortIdArg: string
): SocketRequest<any> {
return smartsocketRef.socketRequests.find((socketRequestArg) => {
return socketRequestArg.shortid === shortIdArg;
});
}
// INSTANCE
public status: TSocketRequestStatus = 'new';
public side: TSocketRequestSide;
public shortid: string;
public originSocketConnection: SocketConnection;
public funcCallData: ISocketFunctionCallDataRequest<T>;
public done = plugins.smartpromise.defer<ISocketFunctionCallDataResponse<T>>();
public smartsocketRef: Smartsocket | SmartsocketClient;
constructor(
smartsocketRefArg: Smartsocket | SmartsocketClient,
optionsArg: ISocketRequestConstructorOptions<T>
) {
this.smartsocketRef = smartsocketRefArg;
this.side = optionsArg.side;
this.shortid = optionsArg.shortId;
this.funcCallData = optionsArg.funcCallData;
this.originSocketConnection = optionsArg.originSocketConnection;
allSocketRequests.add(this);
this.smartsocketRef.socketRequests.add(this);
}
// requesting --------------------------
@ -56,10 +77,10 @@ export class SocketRequest {
/**
* dispatches a socketrequest from the requesting to the receiving side
*/
dispatch() {
let requestData: ISocketRequestDataObject = {
public dispatch(): Promise<ISocketFunctionCallDataResponse<T>> {
const requestData: ISocketRequestDataObject<T> = {
funcCallData: this.funcCallData,
shortId: this.shortid
shortId: this.shortid,
};
this.originSocketConnection.socket.emit('function', requestData);
return this.done.promise;
@ -68,10 +89,10 @@ export class SocketRequest {
/**
* handles the response that is received by the requesting side
*/
handleResponse(responseDataArg: ISocketRequestDataObject) {
plugins.smartlog.defaultLogger.log('info', 'handling response!');
public async handleResponse(responseDataArg: ISocketRequestDataObject<T>) {
// logger.log('info', 'handling response!');
this.done.resolve(responseDataArg.funcCallData);
allSocketRequests.remove(this);
this.smartsocketRef.socketRequests.remove(this);
}
// responding --------------------------
@ -79,19 +100,27 @@ export class SocketRequest {
/**
* creates the response on the responding side
*/
createResponse() {
let targetSocketFunction: SocketFunction = helpers.getSocketFunctionByName(
public async createResponse(): Promise<void> {
const targetSocketFunction: SocketFunction<T> = SocketFunction.getSocketFunctionByName(
this.smartsocketRef,
this.funcCallData.funcName
);
plugins.smartlog.defaultLogger.log('info', `invoking ${targetSocketFunction.name}`);
targetSocketFunction.invoke(this.funcCallData).then(resultData => {
plugins.smartlog.defaultLogger.log('info', 'got resultData. Sending it to requesting party.');
let requestData: ISocketRequestDataObject = {
funcCallData: resultData,
shortId: this.shortid
};
this.originSocketConnection.socket.emit('functionResponse', requestData);
allSocketRequests.remove(this);
});
if (!targetSocketFunction) {
logger.log('error', `There is no SocketFunction defined for ${this.funcCallData.funcName}`);
return;
}
// logger.log('info', `invoking ${targetSocketFunction.name}`);
targetSocketFunction
.invoke(this.funcCallData, this.originSocketConnection)
.then((resultData) => {
// logger.log('info', 'got resultData. Sending it to requesting party.');
const responseData: ISocketRequestDataObject<T> = {
funcCallData: resultData,
shortId: this.shortid,
};
this.originSocketConnection.socket.emit('functionResponse', responseData);
this.smartsocketRef.socketRequests.remove(this);
});
}
}

View File

@ -1,13 +1,15 @@
import * as plugins from './smartsocket.plugins';
// import classes
import { Objectmap } from '@pushrocks/lik';
import { SocketFunction } from './smartsocket.classes.socketfunction';
import { Smartsocket } from './smartsocket.classes.smartsocket';
import { SmartsocketClient } from './smartsocket.classes.smartsocketclient';
import { ISocketConnectionAuthenticationObject } from './smartsocket.classes.socketconnection';
/**
* interface for class SocketRole
*/
export interface SocketRoleOptions {
export interface ISocketRoleOptions {
name: string;
passwordHash: string;
}
@ -16,14 +18,40 @@ export interface SocketRoleOptions {
* A socketrole defines access to certain routines.
*/
export class SocketRole {
name: string;
passwordHash: string;
allowedFunctions = new Objectmap<SocketFunction>();
constructor(optionsArg: SocketRoleOptions) {
// STATIC
public static getSocketRoleByName(
referenceSmartsocket: Smartsocket | SmartsocketClient,
socketRoleNameArg: string
): SocketRole {
return referenceSmartsocket.socketRoles.find((socketRoleArg) => {
return socketRoleArg.name === socketRoleNameArg;
});
}
public static async checkPasswordForRole(
dataArg: ISocketConnectionAuthenticationObject,
referenceSmartsocket: Smartsocket | SmartsocketClient
): Promise<boolean> {
const targetPasswordHash = SocketRole.getSocketRoleByName(referenceSmartsocket, dataArg.role)
.passwordHash;
const computedCompareHash = await plugins.isohash.sha256FromString(dataArg.password);
return targetPasswordHash === computedCompareHash;
}
// INSTANCE
public name: string;
public passwordHash: string;
public allowedFunctions = new plugins.lik.ObjectMap<SocketFunction<any>>();
constructor(optionsArg: ISocketRoleOptions) {
this.name = optionsArg.name;
this.passwordHash = optionsArg.passwordHash;
}
addSocketFunction(socketFunctionArg: SocketFunction) {
/**
* adds the socketfunction to the socketrole
* @param socketFunctionArg
*/
public addSocketFunction(socketFunctionArg: SocketFunction<any>) {
this.allowedFunctions.add(socketFunctionArg);
}
}

View File

@ -1,8 +1,9 @@
import * as plugins from './smartsocket.plugins';
import * as pluginsTyped from './smartsocket.pluginstyped';
// used in case no other server is supplied
import * as http from 'http';
import { Smartsocket } from './smartsocket.classes.smartsocket';
import { logger } from './smartsocket.logging';
/**
* class socketServer
@ -10,10 +11,9 @@ import { Smartsocket } from './smartsocket.classes.smartsocket';
*/
export class SocketServer {
private smartsocket: Smartsocket;
private httpServer: http.Server;
private httpServer: pluginsTyped.http.Server | pluginsTyped.https.Server;
// wether httpServer is standalone
private standaloneServer = false;
private expressServer: any;
constructor(smartSocketInstance: Smartsocket) {
this.smartsocket = smartSocketInstance;
@ -23,12 +23,12 @@ export class SocketServer {
* starts the server with another server
* also works with an express style server
*/
public async setExternalServer(serverType: 'express' | 'http', serverArg: any) {
if (serverType === 'http') {
this.httpServer = serverArg;
} else if (serverType === 'express') {
this.expressServer = serverArg;
}
public async setExternalServer(
serverType: 'smartexpress',
serverArg: pluginsTyped.smartexpress.Server
) {
await serverArg.startedPromise;
this.httpServer = serverArg.httpServer;
}
/**
@ -37,14 +37,11 @@ export class SocketServer {
public getServerForSocketIo() {
if (this.httpServer) {
return this.httpServer;
} else if (this.expressServer) {
return this.expressServer;
} else if (!this.httpServer && !this.expressServer) {
this.httpServer = new http.Server();
} else {
const httpModule = this.smartsocket.smartenv.getSafeNodeModule('http');
this.httpServer = new httpModule.Server();
this.standaloneServer = true;
return this.httpServer;
} else {
throw new Error('no server specified!');
}
}
@ -58,11 +55,14 @@ export class SocketServer {
// in case an external server has been set "this.standaloneServer" should be false
if (this.httpServer && this.standaloneServer) {
if (!this.smartsocket.options.port) {
console.log('there should be a port specifed for smartsocket!')
throw new Error('there should be a port specified for smartsocket')
logger.log('error', 'there should be a port specifed for smartsocket!');
throw new Error('there should be a port specified for smartsocket');
}
this.httpServer.listen(this.smartsocket.options.port, () => {
console.log(`Server started in standalone mode on ${this.smartsocket.options.port}`);
logger.log(
'success',
`Server started in standalone mode on ${this.smartsocket.options.port}`
);
done.resolve();
});
} else {

View File

@ -0,0 +1,3 @@
import * as plugins from './smartsocket.plugins';
export class SocketStats {}

View File

@ -1,60 +0,0 @@
import * as plugins from './smartsocket.plugins';
// classes
import { Smartsocket } from './smartsocket.classes.smartsocket';
import { SocketFunction, allSocketFunctions } from './smartsocket.classes.socketfunction';
import {
SocketConnection,
ISocketConnectionAuthenticationObject
} from './smartsocket.classes.socketconnection';
import {
SocketRequest,
allSocketRequests,
TSocketRequestSide
} from './smartsocket.classes.socketrequest';
import { SocketRole } from './smartsocket.classes.socketrole';
// SocketConnection helpers
export let checkPasswordForRole = (
dataArg: ISocketConnectionAuthenticationObject,
referenceSmartsocket: Smartsocket
): boolean => {
let targetPasswordHash = getSocketRoleByName(dataArg.role, referenceSmartsocket).passwordHash;
let computedCompareHash = plugins.smarthash.sha256FromStringSync(dataArg.password);
return targetPasswordHash === computedCompareHash;
};
// SocketFunction helpers
export let getSocketFunctionByName = (functionNameArg: string): SocketFunction => {
return allSocketFunctions.find(socketFunctionArg => {
return socketFunctionArg.name === functionNameArg;
});
};
// SocketRequest helpers
/**
* get corresponding Socketrequest instance by shortId
*/
export let getSocketRequestById = (
shortIdArg: string,
requestSide?: TSocketRequestSide
): SocketRequest => {
return allSocketRequests.find(socketRequestArg => {
return socketRequestArg.shortid === shortIdArg;
});
};
// SocketRole helpers
/**
* get corresponding SocketRole instance by name
*/
export let getSocketRoleByName = (
socketRoleNameArg: string,
referenceSmartsocket: Smartsocket
): SocketRole => {
return referenceSmartsocket.socketRoles.find(socketRoleArg => {
return socketRoleArg.name === socketRoleNameArg;
});
};

View File

@ -0,0 +1,3 @@
import * as plugins from './smartsocket.plugins';
export const logger = new plugins.smartlog.ConsoleLog();

View File

@ -1,10 +1,27 @@
// apiglobal scope
import * as typedrequestInterfaces from '@apiglobal/typedrequest-interfaces';
export { typedrequestInterfaces };
// pushrocks scope
import * as isohash from '@pushrocks/isohash';
import * as isounique from '@pushrocks/isounique';
import * as lik from '@pushrocks/lik';
import * as smartenv from '@pushrocks/smartenv';
import * as smartlog from '@pushrocks/smartlog';
import * as smarthash from '@pushrocks/smarthash';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartpromise from '@pushrocks/smartpromise';
import * as shortid from 'shortid';
import * as socketIo from 'socket.io';
import * as socketIoClient from 'socket.io-client';
import * as smarttime from '@pushrocks/smarttime';
import * as smartrx from '@pushrocks/smartrx';
export { lik, smartlog, smarthash, smartdelay, smartpromise, shortid, socketIo, socketIoClient };
export {
isohash,
isounique,
lik,
smartenv,
smartlog,
smartdelay,
smartpromise,
smarttime,
smartrx,
};

View File

@ -0,0 +1,16 @@
// node native
import type http from 'http';
import type https from 'https';
export { http, https };
// pushrocks scope
import type * as smartexpress from '@pushrocks/smartexpress';
export { smartexpress };
// third party scope
import type socketIo from 'socket.io';
import type socketIoClient from 'socket.io-client';
export { socketIo, socketIoClient };