@interopio/gateway-server 0.4.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +94 -0
- package/dist/gateway-ent.cjs +305 -0
- package/dist/gateway-ent.cjs.map +7 -0
- package/dist/gateway-ent.js +277 -0
- package/dist/gateway-ent.js.map +7 -0
- package/dist/index.cjs +1713 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +1682 -0
- package/dist/index.js.map +7 -0
- package/dist/metrics-rest.cjs +21440 -0
- package/dist/metrics-rest.cjs.map +7 -0
- package/dist/metrics-rest.js +21430 -0
- package/dist/metrics-rest.js.map +7 -0
- package/gateway-server.d.ts +69 -0
- package/package.json +66 -0
- package/readme.md +9 -0
- package/src/common/compose.ts +40 -0
- package/src/gateway/ent/config.ts +174 -0
- package/src/gateway/ent/index.ts +18 -0
- package/src/gateway/ent/logging.ts +89 -0
- package/src/gateway/ent/server.ts +34 -0
- package/src/gateway/metrics/rest.ts +20 -0
- package/src/gateway/ws/core.ts +90 -0
- package/src/index.ts +3 -0
- package/src/logger.ts +6 -0
- package/src/mesh/connections.ts +101 -0
- package/src/mesh/rest-directory/routes.ts +38 -0
- package/src/mesh/ws/broker/core.ts +163 -0
- package/src/mesh/ws/cluster/core.ts +107 -0
- package/src/mesh/ws/relays/core.ts +159 -0
- package/src/metrics/routes.ts +86 -0
- package/src/server/address.ts +47 -0
- package/src/server/cors.ts +311 -0
- package/src/server/exchange.ts +379 -0
- package/src/server/monitoring.ts +167 -0
- package/src/server/types.ts +69 -0
- package/src/server/ws-client-verify.ts +79 -0
- package/src/server.ts +316 -0
- package/src/utils.ts +10 -0
- package/types/gateway-ent.d.ts +212 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {IOGateway} from '@interopio/gateway';
|
|
2
|
+
import {FilePublisherConfig} from '@interopio/gateway/metrics/publisher/file';
|
|
3
|
+
|
|
4
|
+
export default GatewayServer.Factory;
|
|
5
|
+
|
|
6
|
+
export namespace GatewayServer {
|
|
7
|
+
export const Factory: (options: ServerConfig) => Promise<Server>;
|
|
8
|
+
export type SslConfig = {key?: string, cert?: string, ca?: string};
|
|
9
|
+
export type OriginFilters = {
|
|
10
|
+
non_matched?: IOGateway.Filtering.Action
|
|
11
|
+
missing?: IOGateway.Filtering.Action
|
|
12
|
+
block?: IOGateway.Filtering.Matcher[]
|
|
13
|
+
allow?: IOGateway.Filtering.Matcher[]
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated
|
|
16
|
+
* @see block
|
|
17
|
+
*/
|
|
18
|
+
blacklist?: IOGateway.Filtering.Matcher[]
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated
|
|
21
|
+
* @see allow
|
|
22
|
+
*/
|
|
23
|
+
whitelist?: IOGateway.Filtering.Matcher[]
|
|
24
|
+
}
|
|
25
|
+
export import LoggerConfig = IOGateway.Logging.LogConfig;
|
|
26
|
+
export type ServerConfig = {
|
|
27
|
+
/**
|
|
28
|
+
* The port to bind for network communication.
|
|
29
|
+
* Accepts a single valuer or a range.
|
|
30
|
+
* If a range is specified, will bind to the first available port in the range.
|
|
31
|
+
*
|
|
32
|
+
* Defaults to 0 - a random port.
|
|
33
|
+
*/
|
|
34
|
+
port?: number | string
|
|
35
|
+
/**
|
|
36
|
+
* The network address.
|
|
37
|
+
*/
|
|
38
|
+
host?: string
|
|
39
|
+
ssl?: SslConfig,
|
|
40
|
+
|
|
41
|
+
memory?: {
|
|
42
|
+
memory_limit?: number
|
|
43
|
+
dump_location?: string,
|
|
44
|
+
dump_prefix?: string,
|
|
45
|
+
report_interval?: number,
|
|
46
|
+
max_backups?: number,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
gateway?: IOGateway.GatewayConfig & {
|
|
50
|
+
route?: string
|
|
51
|
+
ping?: number
|
|
52
|
+
origins?: OriginFilters
|
|
53
|
+
limits?: {
|
|
54
|
+
max_connections?: number
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
mesh?: {
|
|
58
|
+
timeout?: number // defaults to 60000
|
|
59
|
+
ping?: number // defaults to 30000
|
|
60
|
+
}
|
|
61
|
+
metrics?: {
|
|
62
|
+
file: FilePublisherConfig
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export interface Server {
|
|
66
|
+
readonly gateway: IOGateway.Gateway
|
|
67
|
+
close(): Promise<void>
|
|
68
|
+
}
|
|
69
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@interopio/gateway-server",
|
|
3
|
+
"version": "0.4.0-beta",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"gateway",
|
|
6
|
+
"server",
|
|
7
|
+
"mesh",
|
|
8
|
+
"io.connect",
|
|
9
|
+
"glue42",
|
|
10
|
+
"interop.io"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://interop.io/",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "interop.io",
|
|
15
|
+
"url": "https://interop.io/"
|
|
16
|
+
},
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"start": "node .",
|
|
20
|
+
"test": "mocha test --recursive",
|
|
21
|
+
"prepare": "node scripts/prepare.mjs",
|
|
22
|
+
"build": "node scripts/build.mjs",
|
|
23
|
+
"prepack": "npm run build",
|
|
24
|
+
"clean": "node scripts/clean.mjs",
|
|
25
|
+
"coverage": "c8 --reporter=lcov --src=./src npm run test"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./gateway-server.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"node": "./dist/index.cjs",
|
|
33
|
+
"require": "./dist/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./metrics-rest": {
|
|
36
|
+
"import": "./dist/metrics-rest.js",
|
|
37
|
+
"node": "./dist/metrics-rest.cjs",
|
|
38
|
+
"require": "./dist/metrics-rest.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./gateway-ent": {
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./types/gateway-ent.d.ts",
|
|
43
|
+
"default": "./dist/gateway-ent.js"
|
|
44
|
+
},
|
|
45
|
+
"node": "./dist/gateway-ent.cjs",
|
|
46
|
+
"require": "./dist/gateway-ent.cjs"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"main": "dist/index.js",
|
|
50
|
+
"types": "gateway-server.d.ts",
|
|
51
|
+
"type": "module",
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@interopio/gateway": "^0.9.0-beta",
|
|
54
|
+
"ws": "^8.18.2",
|
|
55
|
+
"http-cookie-agent": "^6.0.8",
|
|
56
|
+
"undici": "^6.21.3",
|
|
57
|
+
"tough-cookie": "^5.0.0"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18.17"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/transit-js": "^0.8.3",
|
|
64
|
+
"@types/ws": "^8.18.1"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
// https://github.com/koajs/compose/blob/master/index.js
|
|
3
|
+
/**
|
|
4
|
+
* @typeParam T - Type of context passed through the middleware
|
|
5
|
+
* @param middleware middleware stack
|
|
6
|
+
* @return {Callback}
|
|
7
|
+
*/
|
|
8
|
+
export function compose<T>(...middleware: (((ctx: T, next: () => Promise<void>) => Promise<void>) | ((ctx: T) => Promise<void>))[]): (ctx: T) => Promise<void> {
|
|
9
|
+
if (!Array.isArray(middleware)) {
|
|
10
|
+
throw new Error('middleware must be array!');
|
|
11
|
+
}
|
|
12
|
+
for (const fn of middleware) {
|
|
13
|
+
if (typeof fn !== 'function') {
|
|
14
|
+
throw new Error('middleware must be compose of functions!');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return async function (ctx: T, next?: () => Promise<void>) {
|
|
18
|
+
let index = -1;
|
|
19
|
+
return await dispatch(0);
|
|
20
|
+
|
|
21
|
+
async function dispatch(i: number): Promise<void> {
|
|
22
|
+
if (i < index) {
|
|
23
|
+
throw new Error('next() called multiple times');
|
|
24
|
+
}
|
|
25
|
+
index = i;
|
|
26
|
+
let fn: ((() => Promise<void>) | undefined) | ((c: T, n: () => Promise<void>) => Promise<void>);
|
|
27
|
+
if (i === middleware.length) {
|
|
28
|
+
fn = next;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
fn = middleware[i];
|
|
32
|
+
}
|
|
33
|
+
if (!fn) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await fn(ctx, dispatch.bind(null, i + 1));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {GatewayServer} from '../../../gateway-server';
|
|
2
|
+
import {IOGateway} from '@interopio/gateway';
|
|
3
|
+
import { GatewayConfig, MetricsPublisherConfig } from '../../../types/gateway-ent';
|
|
4
|
+
|
|
5
|
+
function toMetricsFilters(legacy?: MetricsPublisherConfig['filters']): IOGateway.MetricFilters | undefined {
|
|
6
|
+
if (legacy) {
|
|
7
|
+
const publishers = legacy.publishers.map(publisher => {
|
|
8
|
+
return {identity: publisher.publisher, metrics: publisher.metrics};
|
|
9
|
+
});
|
|
10
|
+
const non_matched = legacy['non-matched'];
|
|
11
|
+
return {publishers, non_matched};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toMetricsPublisherConfig(legacy: MetricsPublisherConfig & {
|
|
16
|
+
conflation?: { 'max-datapoints-repo'?: number } & MetricsPublisherConfig['conflation']
|
|
17
|
+
}): IOGateway.BasicMetricsPublisherConfig {
|
|
18
|
+
const filters = toMetricsFilters(legacy?.filters);
|
|
19
|
+
const conflation = toConflation(legacy?.conflation);
|
|
20
|
+
return {...legacy, filters, conflation};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function toConflation(legacy?: {
|
|
24
|
+
'max-datapoints-repo'?: number
|
|
25
|
+
} & MetricsPublisherConfig['conflation']): IOGateway.BasicMetricsPublisherConfig['conflation'] | undefined {
|
|
26
|
+
if (legacy) {
|
|
27
|
+
return {interval: legacy.interval};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toMetricsConfig(legacy: GatewayConfig['metrics']): IOGateway.GatewayConfig['metrics'] {
|
|
32
|
+
const metrics: IOGateway.GatewayConfig['metrics'] = {publishers: []};
|
|
33
|
+
legacy?.publishers?.forEach((publisher) => {
|
|
34
|
+
if (typeof publisher === 'string') {
|
|
35
|
+
if (publisher === 'rest') {
|
|
36
|
+
metrics.publishers.push('rest');
|
|
37
|
+
if (legacy.rest) {
|
|
38
|
+
const conf = {...legacy.rest};
|
|
39
|
+
const userAgent = conf["user-agent"];
|
|
40
|
+
delete conf['user-agent'];
|
|
41
|
+
const headers = {...conf.headers, ...(userAgent ? {'user-agent': userAgent} : {})};
|
|
42
|
+
delete conf.headers;
|
|
43
|
+
metrics.rest = {
|
|
44
|
+
endpoint: conf.endpoint,
|
|
45
|
+
headers: headers,
|
|
46
|
+
...toMetricsPublisherConfig(conf)
|
|
47
|
+
};
|
|
48
|
+
metrics.rest['publishFn'] ??= '@interopio/gateway-server/metrics-rest'
|
|
49
|
+
}
|
|
50
|
+
} else if (publisher === 'file') {
|
|
51
|
+
metrics.publishers.push('file');
|
|
52
|
+
if (legacy.file) {
|
|
53
|
+
const conf = {...legacy.file};
|
|
54
|
+
const status = conf['skip-status'] === undefined ? true : !conf['skip-status'];
|
|
55
|
+
delete conf['skip-status'];
|
|
56
|
+
metrics.file = {
|
|
57
|
+
location: conf.location,
|
|
58
|
+
status: status,
|
|
59
|
+
...toMetricsPublisherConfig(conf)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
// unsupported predefined type
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
const configuration = {...publisher.configuration};
|
|
67
|
+
const splitSize = configuration["split-size"];
|
|
68
|
+
delete configuration["split-size"];
|
|
69
|
+
const file = publisher['file'] as string;
|
|
70
|
+
const custom: IOGateway.CustomMetricsPublisherConfig = {
|
|
71
|
+
split_size: splitSize,
|
|
72
|
+
publisher: {file, configuration},
|
|
73
|
+
...toMetricsPublisherConfig(configuration)
|
|
74
|
+
}
|
|
75
|
+
metrics.publishers.push(custom);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (legacy?.filters) {
|
|
79
|
+
metrics.filters = toMetricsFilters(legacy?.filters);
|
|
80
|
+
}
|
|
81
|
+
return metrics;
|
|
82
|
+
}
|
|
83
|
+
type GatewayCluster = Required<GatewayConfig>['cluster'];
|
|
84
|
+
type P2P = Required<GatewayCluster>['p2p'];
|
|
85
|
+
|
|
86
|
+
function toCluster(legacy?: P2P): IOGateway.MeshConfig['cluster'] | undefined {
|
|
87
|
+
if (legacy?.directory) {
|
|
88
|
+
const legacyDirectory = legacy.directory;
|
|
89
|
+
const config: IOGateway.MeshConfig['cluster'] = {endpoint: legacy?.['endpoint']};
|
|
90
|
+
if (legacyDirectory.type === 'rest') {
|
|
91
|
+
let directory: IOGateway.RestMeshDirectoryConfig | undefined = undefined;
|
|
92
|
+
if (config.endpoint === undefined) {
|
|
93
|
+
config.endpoint = legacyDirectory.config!.directory_uri!;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
directory = {uri: legacyDirectory.config!.directory_uri};
|
|
97
|
+
}
|
|
98
|
+
if (legacyDirectory.config?.announce_interval) {
|
|
99
|
+
directory ??= {};
|
|
100
|
+
directory.interval = Number(legacyDirectory.config.announce_interval);
|
|
101
|
+
}
|
|
102
|
+
if (directory !== undefined) {
|
|
103
|
+
config.directory = directory;
|
|
104
|
+
}
|
|
105
|
+
return config;
|
|
106
|
+
}
|
|
107
|
+
else if (legacyDirectory.type === 'static') {
|
|
108
|
+
config.directory = {members : legacyDirectory.members ?? []};
|
|
109
|
+
return config;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function toMeshConfig(legacy: GatewayConfig['cluster']) {
|
|
115
|
+
const mesh: IOGateway.MeshConfig = {};
|
|
116
|
+
if (legacy?.configuration?.node_id) mesh.node = legacy.configuration.node_id;
|
|
117
|
+
if (legacy?.type === 'broker') mesh.broker = {endpoint: legacy.broker!.endpoint!};
|
|
118
|
+
if (legacy?.type === 'p2p') {
|
|
119
|
+
const cluster = toCluster(legacy.p2p);
|
|
120
|
+
if (cluster) {
|
|
121
|
+
mesh.cluster = cluster;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return mesh;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function toServerConfig(config: GatewayConfig): GatewayServer.ServerConfig {
|
|
128
|
+
const gateway: GatewayServer.ServerConfig["gateway"] = {
|
|
129
|
+
route: config.route,
|
|
130
|
+
limits: config.limits,
|
|
131
|
+
origins: config.security?.origin_filters as GatewayServer.OriginFilters,
|
|
132
|
+
clients: config['clients'] ?? {inactive_seconds: 0, buffer_size: 100},
|
|
133
|
+
contexts: {
|
|
134
|
+
lifetime: 'retained',
|
|
135
|
+
visibility: [
|
|
136
|
+
{context: /___channel___.+/, restrictions: 'cluster'},
|
|
137
|
+
{context: /T42\..+/, restrictions: 'local'}
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
methods: {
|
|
141
|
+
visibility: [
|
|
142
|
+
{method: /T42\..+/, restrictions: 'local'}
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
peers: {
|
|
146
|
+
visibility: [
|
|
147
|
+
{domain: 'context', restrictions: 'cluster'},
|
|
148
|
+
{domain: 'agm', restrictions: 'local'}
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
metrics: {publishers: []}
|
|
152
|
+
};
|
|
153
|
+
if (config.authentication?.token_ttl) gateway.token = {ttl: config.authentication?.token_ttl as number};
|
|
154
|
+
if (config['contexts']) {
|
|
155
|
+
gateway.contexts = config['contexts'];
|
|
156
|
+
}
|
|
157
|
+
if (config['methods']) {
|
|
158
|
+
gateway.methods = config['methods'];
|
|
159
|
+
}
|
|
160
|
+
if (config['peers']) {
|
|
161
|
+
gateway.peers = config['peers'];
|
|
162
|
+
}
|
|
163
|
+
if (config.cluster?.enabled) {
|
|
164
|
+
gateway.mesh = toMeshConfig(config.cluster);
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
if (config.metrics?.publishers) gateway.metrics = toMetricsConfig(config.metrics);
|
|
168
|
+
return {
|
|
169
|
+
port: config.port ?? 3434,
|
|
170
|
+
host: config.ip ?? config['host'],
|
|
171
|
+
memory: config['memory'],
|
|
172
|
+
gateway: gateway
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {toLogConfig} from './logging.js';
|
|
2
|
+
import {ServerDelegate} from './server.js';
|
|
3
|
+
import {toServerConfig} from './config.js';
|
|
4
|
+
import {
|
|
5
|
+
Gateway,
|
|
6
|
+
GatewayConfig,
|
|
7
|
+
LogInfo,
|
|
8
|
+
LogLevel
|
|
9
|
+
} from '../../../types/gateway-ent';
|
|
10
|
+
import {IOGateway} from '@interopio/gateway';
|
|
11
|
+
|
|
12
|
+
export function create(config: GatewayConfig): Gateway {
|
|
13
|
+
return new ServerDelegate(toServerConfig(config));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function configure_logging(config?: { level: LogLevel, appender?: (info: LogInfo) => void }) {
|
|
17
|
+
IOGateway.Logging.configure(toLogConfig(config));
|
|
18
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {LogInfo, LogLevel} from '@interopio/gateway-server/gateway-ent';
|
|
2
|
+
import {IOGateway} from '@interopio/gateway';
|
|
3
|
+
import {format} from 'node:util';
|
|
4
|
+
|
|
5
|
+
class LogInfoAdapter implements LogInfo {
|
|
6
|
+
private _parsed?: { err?: Error, msg: string };
|
|
7
|
+
private _timestamp?: string;
|
|
8
|
+
private _output?: string;
|
|
9
|
+
|
|
10
|
+
constructor(private readonly event: IOGateway.Logging.LogEvent) {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private parsed(): { err?: Error, msg: string } {
|
|
14
|
+
if (this._parsed === undefined) {
|
|
15
|
+
let err: Error | undefined = undefined;
|
|
16
|
+
let vargs = this.event.data;
|
|
17
|
+
if (this.event.data[0] instanceof Error) {
|
|
18
|
+
err = this.event.data[0];
|
|
19
|
+
vargs = vargs.slice(1);
|
|
20
|
+
}
|
|
21
|
+
const msg = format(this.event.message, ...vargs);
|
|
22
|
+
this._parsed = {err, msg};
|
|
23
|
+
}
|
|
24
|
+
return this._parsed;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get time(): Date {
|
|
28
|
+
return this.event.time;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get level(): LogLevel {
|
|
32
|
+
return this.event.level;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get namespace() {
|
|
36
|
+
return this.event.name;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get file(): string {
|
|
40
|
+
return undefined as unknown as string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get line(): number {
|
|
44
|
+
return undefined as unknown as number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get message(): string {
|
|
48
|
+
return this.parsed().msg;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get stacktrace(): Error | undefined {
|
|
52
|
+
return this.parsed().err;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private get timestamp(): string {
|
|
56
|
+
if (this._timestamp === undefined) {
|
|
57
|
+
this._timestamp = this.time.toISOString();
|
|
58
|
+
}
|
|
59
|
+
return this._timestamp;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get output(): string {
|
|
63
|
+
if (this._output === undefined) {
|
|
64
|
+
const err = this.parsed().err;
|
|
65
|
+
const stacktrace = err ? `\n${err.stack ?? err}` : '';
|
|
66
|
+
this._output = `${this.timestamp} ${this.level.toUpperCase()} [${this.namespace}] - ${this.message}${stacktrace}`;
|
|
67
|
+
}
|
|
68
|
+
return this._output;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function toLogConfig(config?: { level?: LogLevel; appender?: (info: LogInfo) => void }): IOGateway.Logging.LogConfig {
|
|
73
|
+
let level: Exclude<LogLevel, 'report' | 'fatal'> = 'info';
|
|
74
|
+
if (config?.level) {
|
|
75
|
+
if (config.level === 'fatal') {
|
|
76
|
+
level = 'error';
|
|
77
|
+
} else if (config.level !== 'report') {
|
|
78
|
+
level = config.level;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const result: IOGateway.Logging.LogConfig = {level};
|
|
82
|
+
const appenderFn = config?.appender;
|
|
83
|
+
if (appenderFn) {
|
|
84
|
+
result.appender = (event: IOGateway.Logging.LogEvent) => {
|
|
85
|
+
appenderFn(new LogInfoAdapter(event));
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {Gateway, GatewayClient, GatewayMessage} from '@interopio/gateway-server/gateway-ent';
|
|
2
|
+
import {GatewayServer} from '@interopio/gateway-server';
|
|
3
|
+
|
|
4
|
+
export class ServerDelegate implements Gateway {
|
|
5
|
+
private server?: GatewayServer.Server;
|
|
6
|
+
|
|
7
|
+
constructor(private readonly config: GatewayServer.ServerConfig) {
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async connect(cb: (client: GatewayClient, msg: GatewayMessage) => void): Promise<GatewayClient> {
|
|
11
|
+
if (!this.server) {
|
|
12
|
+
throw new Error(`not started`);
|
|
13
|
+
}
|
|
14
|
+
const client = await this.server.gateway.connect((c, m) => cb(c as unknown as GatewayClient, m as GatewayMessage));
|
|
15
|
+
return client as unknown as GatewayClient;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
info(): { endpoint: string } {
|
|
19
|
+
return this.server?.gateway.info() as { endpoint: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async start(): Promise<Gateway> {
|
|
23
|
+
if (!this.server) {
|
|
24
|
+
this.server = await GatewayServer.Factory(this.config);
|
|
25
|
+
}
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async stop(): Promise<Gateway> {
|
|
30
|
+
await this.server?.close();
|
|
31
|
+
delete this.server;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as rest from '@interopio/gateway/metrics/publisher/rest';
|
|
2
|
+
import {IOGateway} from '@interopio/gateway';
|
|
3
|
+
import {CookieAgent} from 'http-cookie-agent/undici';
|
|
4
|
+
import {CookieJar} from 'tough-cookie';
|
|
5
|
+
|
|
6
|
+
export const fetchWithCookies = (existing?: rest.Fetch): rest.Fetch => {
|
|
7
|
+
const fetchFn = existing ?? rest.fetchWithTimeout();
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
const jar: any = new CookieJar();
|
|
10
|
+
const dispatcher = new CookieAgent({cookies: {jar}});
|
|
11
|
+
return async (endpoint: string | URL, request: Omit<RequestInit, 'signal'>, timeout: number) => {
|
|
12
|
+
const requestWithDispatcher = {...request, dispatcher};
|
|
13
|
+
return await fetchFn(endpoint, requestWithDispatcher, timeout);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export const name = rest.name;
|
|
17
|
+
|
|
18
|
+
export function create(cfg: rest.RestPublisherConfig, logger: IOGateway.Logging.Logger) {
|
|
19
|
+
return rest.create({...cfg, fetch: fetchWithCookies(cfg.fetch)}, logger);
|
|
20
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as ws from 'ws';
|
|
2
|
+
import getLogger from '../../logger.js';
|
|
3
|
+
import {socketKey} from '../../utils.js';
|
|
4
|
+
import {HttpServerRequest} from '../../server/exchange.js';
|
|
5
|
+
import {IOGateway} from '@interopio/gateway';
|
|
6
|
+
import GatewayEncoders = IOGateway.Encoding;
|
|
7
|
+
|
|
8
|
+
const log = getLogger('ws');
|
|
9
|
+
const codec = GatewayEncoders.json<IOGateway.Message>();
|
|
10
|
+
|
|
11
|
+
function initClient(this: IOGateway.Gateway, key: string, socket: ws.WebSocket, host?: string): IOGateway.GatewayClient<string> | undefined {
|
|
12
|
+
const opts = {
|
|
13
|
+
key,
|
|
14
|
+
host,
|
|
15
|
+
codec,
|
|
16
|
+
onPing: () => {
|
|
17
|
+
socket.ping((err?: Error) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
log.warn(`failed to ping ${key}`, err);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
log.info(`ping sent to ${key}`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
onDisconnect: (reason: 'inactive' | 'shutdown') => {
|
|
27
|
+
switch (reason) {
|
|
28
|
+
case 'inactive': {
|
|
29
|
+
log.warn(`no heartbeat (ping) received from ${key}, closing socket`);
|
|
30
|
+
socket.close(4001, 'ping expected');
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case 'shutdown': {
|
|
34
|
+
socket.close(1001, 'shutdown');
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
return this.client((data) => socket.send(data), opts);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
log.warn(`${key} failed to create client`, err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function create(this: IOGateway.Gateway, server: {endpoint: string, wss: ws.WebSocketServer}): Promise<{ close: () => Promise<void> }> {
|
|
48
|
+
log.info('start gateway');
|
|
49
|
+
await this.start({endpoint: server.endpoint});
|
|
50
|
+
|
|
51
|
+
server.wss
|
|
52
|
+
.on('error', (err: Error) => {
|
|
53
|
+
log.error('error starting the gateway websocket server', err);
|
|
54
|
+
})
|
|
55
|
+
.on('connection', (socket, req) => {
|
|
56
|
+
const request = new HttpServerRequest(req);
|
|
57
|
+
const key = socketKey(request.socket);
|
|
58
|
+
const host = request.host;
|
|
59
|
+
log.info(`${key} connected on gw from ${host}`);
|
|
60
|
+
const client = initClient.call(this, key, socket);
|
|
61
|
+
if (!client) {
|
|
62
|
+
log.error(`${key} gw client init failed`);
|
|
63
|
+
socket.terminate();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
socket.on('error', (err: Error) => {
|
|
67
|
+
log.error(`${key} websocket error: ${err}`, err);
|
|
68
|
+
});
|
|
69
|
+
socket.on('message', (data, _isBinary) => {
|
|
70
|
+
if (Array.isArray(data)) {
|
|
71
|
+
data = Buffer.concat(data);
|
|
72
|
+
}
|
|
73
|
+
// JSON.parse will invoke abstract operation ToString (coerce it) prior parsing (https://262.ecma-international.org/5.1/#sec-15.12.2)
|
|
74
|
+
client.send(data as unknown as string);
|
|
75
|
+
|
|
76
|
+
});
|
|
77
|
+
socket.on('close', (code) => {
|
|
78
|
+
log.info(`${key} disconnected from gw. code: ${code}`);
|
|
79
|
+
client.close();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
close: async () => {
|
|
84
|
+
server.wss.close();
|
|
85
|
+
await this.stop();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default create;
|
package/src/index.ts
ADDED