@sellout/service 0.0.1
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/.dist/BaseService.d.ts +22 -0
- package/.dist/BaseService.js +77 -0
- package/.dist/BaseService.js.map +1 -0
- package/.dist/ConsoleLogManager.d.ts +9 -0
- package/.dist/ConsoleLogManager.js +18 -0
- package/.dist/ConsoleLogManager.js.map +1 -0
- package/.dist/MongoConnectionManager.d.ts +9 -0
- package/.dist/MongoConnectionManager.js +52 -0
- package/.dist/MongoConnectionManager.js.map +1 -0
- package/.dist/NatsConnectionManager.d.ts +115 -0
- package/.dist/NatsConnectionManager.js +215 -0
- package/.dist/NatsConnectionManager.js.map +1 -0
- package/.dist/PbAsyncMessageHandler.d.ts +15 -0
- package/.dist/PbAsyncMessageHandler.js +26 -0
- package/.dist/PbAsyncMessageHandler.js.map +1 -0
- package/.dist/PbBroadcastProxy.d.ts +25 -0
- package/.dist/PbBroadcastProxy.js +41 -0
- package/.dist/PbBroadcastProxy.js.map +1 -0
- package/.dist/PbMessageHandler.d.ts +26 -0
- package/.dist/PbMessageHandler.js +46 -0
- package/.dist/PbMessageHandler.js.map +1 -0
- package/.dist/PbServiceProxy.d.ts +38 -0
- package/.dist/PbServiceProxy.js +64 -0
- package/.dist/PbServiceProxy.js.map +1 -0
- package/.dist/Segment.d.ts +12 -0
- package/.dist/Segment.js +34 -0
- package/.dist/Segment.js.map +1 -0
- package/.dist/Tracer.d.ts +22 -0
- package/.dist/Tracer.js +49 -0
- package/.dist/Tracer.js.map +1 -0
- package/.dist/TracerExpress.d.ts +1 -0
- package/.dist/TracerExpress.js +42 -0
- package/.dist/TracerExpress.js.map +1 -0
- package/.dist/build/tsconfig.json +32 -0
- package/.dist/build/tslint.json +17 -0
- package/.dist/env.d.ts +5 -0
- package/.dist/env.js +8 -0
- package/.dist/env.js.map +1 -0
- package/.dist/interfaces.d.ts +31 -0
- package/.dist/interfaces.js +3 -0
- package/.dist/interfaces.js.map +1 -0
- package/.dist/joiToErrors.d.ts +3 -0
- package/.dist/joiToErrors.js +43 -0
- package/.dist/joiToErrors.js.map +1 -0
- package/.dist/schemas.d.ts +0 -0
- package/.dist/schemas.js +1 -0
- package/.dist/schemas.js.map +1 -0
- package/package.json +34 -0
- package/src/BaseService.ts +98 -0
- package/src/ConsoleLogManager.ts +23 -0
- package/src/MongoConnectionManager.ts +49 -0
- package/src/NatsConnectionManager.ts +268 -0
- package/src/PbAsyncMessageHandler.ts +28 -0
- package/src/PbBroadcastProxy.ts +41 -0
- package/src/PbMessageHandler.ts +49 -0
- package/src/PbServiceProxy.ts +66 -0
- package/src/Segment.ts +35 -0
- package/src/Tracer.ts +55 -0
- package/src/TracerExpress.ts +36 -0
- package/src/build/tsconfig.json +32 -0
- package/src/build/tslint.json +17 -0
- package/src/env.ts +5 -0
- package/src/interfaces.ts +47 -0
- package/src/joiToErrors.ts +48 -0
- package/src/schemas.ts +0 -0
- package/tsconfig.json +28 -0
- package/tslint.json +21 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { IConnectionManager, ILogManager, IServiceOpts } from "./interfaces";
|
|
2
|
+
import * as Prometheus from 'prom-client';
|
|
3
|
+
import * as express from 'express';
|
|
4
|
+
import {
|
|
5
|
+
DISABLE_PROMETHEUS,
|
|
6
|
+
SENTRY_DSN,
|
|
7
|
+
NODE_ENV,
|
|
8
|
+
SEGMENT_IO_WRITE_KEY,
|
|
9
|
+
} from "./env";
|
|
10
|
+
import * as Sentry from "@sentry/node";
|
|
11
|
+
import Segment from "./Segment";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Provides the abstract class for all service implementations.
|
|
15
|
+
*/
|
|
16
|
+
class BaseService {
|
|
17
|
+
public opts: IServiceOpts;
|
|
18
|
+
public serviceName: string;
|
|
19
|
+
public connectionMgr: IConnectionManager;
|
|
20
|
+
public logger: ILogManager;
|
|
21
|
+
public storage;
|
|
22
|
+
public segment: Segment;
|
|
23
|
+
public collectDefaultMetrics;
|
|
24
|
+
|
|
25
|
+
constructor(opts: IServiceOpts) {
|
|
26
|
+
this.opts = opts;
|
|
27
|
+
this.connectionMgr = this.opts.connectionMgr;
|
|
28
|
+
this.logger = this.opts.logManager;
|
|
29
|
+
this.serviceName = this.opts.serviceName;
|
|
30
|
+
this.storage = this.opts.storageManager;
|
|
31
|
+
this.segment = new Segment(SEGMENT_IO_WRITE_KEY, this.logger);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Initialize Sentry
|
|
35
|
+
*/
|
|
36
|
+
if (SENTRY_DSN) {
|
|
37
|
+
this.logger.info("Sentry - Initializing with environment ${NODE_ENV}...");
|
|
38
|
+
Sentry.init({
|
|
39
|
+
dsn: SENTRY_DSN,
|
|
40
|
+
environment: NODE_ENV,
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
this.logger.warn("Sentry - No DSN supplied, skipping initialization...");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Enable/Disable Prometheus
|
|
47
|
+
if(!DISABLE_PROMETHEUS) {
|
|
48
|
+
// set up Prometheus client metrics gathering
|
|
49
|
+
const collectDefaultMetrics = Prometheus.collectDefaultMetrics;
|
|
50
|
+
// collectDefaultMetrics({ timeout: 5000 });
|
|
51
|
+
collectDefaultMetrics();
|
|
52
|
+
|
|
53
|
+
// initialize Express app
|
|
54
|
+
const app = express();
|
|
55
|
+
const metricsPort = 5499; // todo: parameterize this
|
|
56
|
+
|
|
57
|
+
// Metrics endpoint
|
|
58
|
+
app.get('/metrics', (req, res) => {
|
|
59
|
+
res.set('Content-Type', Prometheus.register.contentType)
|
|
60
|
+
res.end(Prometheus.register.metrics())
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
app.get('/readiness', (req, res) => {
|
|
64
|
+
if (this.isReady()) {
|
|
65
|
+
res.status(200).send('OK');
|
|
66
|
+
} else {
|
|
67
|
+
res.status(500).send('Not ready');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// as long as the event loop is running this should run
|
|
72
|
+
app.get('/liveness', (req, res) => {
|
|
73
|
+
res.status(200).send('OK');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// listen for metrics requests
|
|
77
|
+
app.listen(metricsPort, () => this.logger.info(`Service '${this.serviceName}' listening for metrics requests on port ${metricsPort}.`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// override to have derived classes declare non-readiness to k8s
|
|
82
|
+
public isReady() : boolean {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Register message listeners here.
|
|
88
|
+
*/
|
|
89
|
+
public register() {
|
|
90
|
+
throw new Error("Not Implemented: register method in Service class.");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public run() {
|
|
94
|
+
throw new Error("Not Implemented: run method in Service class.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default BaseService;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ILogManager, ILogManagerOpts } from "./interfaces";
|
|
2
|
+
|
|
3
|
+
class ConsoleLogManager implements ILogManager {
|
|
4
|
+
public opts: ILogManagerOpts;
|
|
5
|
+
|
|
6
|
+
constructor(opts: ILogManagerOpts) {
|
|
7
|
+
this.opts = opts;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public info(msg: string, ...params: string[]): void {
|
|
11
|
+
console.info(msg, ...params);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public warn(msg: string, ...params: string[]): void {
|
|
15
|
+
console.warn(msg, ...params);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public error(msg: string, ...params: string[]): void {
|
|
19
|
+
console.error(msg, ...params);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default ConsoleLogManager;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import wait from '@sellout/utils/.dist/wait';
|
|
2
|
+
|
|
3
|
+
export default class MongoConnectionManager {
|
|
4
|
+
public connected: boolean;
|
|
5
|
+
public mongoConnectionString: string;
|
|
6
|
+
public isReplicaSet: boolean;
|
|
7
|
+
private mongoose: any;
|
|
8
|
+
private mongoConnectionStringInternal: string;
|
|
9
|
+
|
|
10
|
+
constructor(mongoose: any, mongoConnectionString: string, username: string = '', password: string = '') {
|
|
11
|
+
const parsed = new URL(mongoConnectionString);
|
|
12
|
+
|
|
13
|
+
parsed.username = username;
|
|
14
|
+
parsed.password = password;
|
|
15
|
+
parsed.pathname = '/admin';
|
|
16
|
+
|
|
17
|
+
this.connected = false;
|
|
18
|
+
this.mongoConnectionStringInternal = parsed.toString();
|
|
19
|
+
this.isReplicaSet = parsed.protocol === 'mongodb+srv:';
|
|
20
|
+
|
|
21
|
+
// redact username/password from publicly available connection string
|
|
22
|
+
parsed.username = '__user__';
|
|
23
|
+
parsed.password = '__pass__';
|
|
24
|
+
this.mongoConnectionString = parsed.toString();
|
|
25
|
+
this.mongoose = mongoose;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async connect() {
|
|
29
|
+
while(!this.connected) {
|
|
30
|
+
console.log('Attempting to connect to Mongo...');
|
|
31
|
+
this.mongoose.connect(this.mongoConnectionStringInternal, {
|
|
32
|
+
ssl: false,
|
|
33
|
+
useUnifiedTopology: true,
|
|
34
|
+
useNewUrlParser: true,
|
|
35
|
+
})
|
|
36
|
+
.then(() => {
|
|
37
|
+
this.connected = true;
|
|
38
|
+
console.log(`Connected to MongoDB: ${this.mongoConnectionString}`);
|
|
39
|
+
})
|
|
40
|
+
.catch((e: any) => {
|
|
41
|
+
console.error(`There was an error connecting to MongoDB: ${this.mongoConnectionString}`);
|
|
42
|
+
console.error(e);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// wait five seconds before trying again
|
|
46
|
+
await wait(5000);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import * as NATS from 'nats';
|
|
3
|
+
import { IConnectionManager, ILogManager, ISubscriptionRoutes } from '@sellout/models/.dist/interfaces';
|
|
4
|
+
|
|
5
|
+
const BROADCAST_PREFIX = 'BROADCAST'; /* Prefix for broadcast messages */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Exposes connection operations for NATS.
|
|
9
|
+
*/
|
|
10
|
+
class NatsConnectionManager implements IConnectionManager {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Exception representing an error that occurred during invocation of
|
|
14
|
+
* a message handler.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
public static MESSAGE_HANDLER_ERROR = class extends Error {
|
|
18
|
+
public errors;
|
|
19
|
+
|
|
20
|
+
constructor(errors, messageSubject: string) {
|
|
21
|
+
super(`Error from message handler for '${messageSubject}'. Reason = ${JSON.stringify(errors)}`);
|
|
22
|
+
this.errors = errors;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Exception caused by request timeout.
|
|
28
|
+
*/
|
|
29
|
+
public static TIMEOUT_ERROR = class extends Error {
|
|
30
|
+
public errors;
|
|
31
|
+
|
|
32
|
+
constructor(errors) {
|
|
33
|
+
super('NATS Timeout');
|
|
34
|
+
this.errors = errors;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
public natsServers: string[];
|
|
39
|
+
private verbose: boolean;
|
|
40
|
+
private defaultRequestTimeout: number;
|
|
41
|
+
|
|
42
|
+
private conn: NATS.Client;
|
|
43
|
+
private logPrefix = 'NatsConnectionManager';
|
|
44
|
+
private logger;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Manages connections to NATS servers.
|
|
48
|
+
* @constructor
|
|
49
|
+
*
|
|
50
|
+
* @param {boolean} verbose - Log all events if true
|
|
51
|
+
*/
|
|
52
|
+
constructor(natsServers: string[], logger: ILogManager, verbose = false, defaultRequestTimeout = 60000) {
|
|
53
|
+
this.logger = logger;
|
|
54
|
+
this.natsServers = natsServers;
|
|
55
|
+
this.verbose = verbose;
|
|
56
|
+
if (process.env.NATS_TIMEOUT) {
|
|
57
|
+
this.defaultRequestTimeout = parseInt(process.env.NATS_TIMEOUT, 10);
|
|
58
|
+
} else {
|
|
59
|
+
this.defaultRequestTimeout = defaultRequestTimeout;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Connect to one of the specified servers.
|
|
65
|
+
*
|
|
66
|
+
* @param {string[]} servers - Array of server URLs for NATS (server is randomly chosen)
|
|
67
|
+
*/
|
|
68
|
+
public connect(waitForConnect = true) {
|
|
69
|
+
const opts: NATS.ClientOpts = {};
|
|
70
|
+
opts.servers = this.natsServers;
|
|
71
|
+
opts.preserveBuffers = true;
|
|
72
|
+
opts.verbose = this.verbose;
|
|
73
|
+
opts.waitOnFirstConnect = waitForConnect;
|
|
74
|
+
opts.maxReconnectAttempts = -1; /* Infinite reconnect attempts */
|
|
75
|
+
|
|
76
|
+
this.conn = NATS.connect(opts);
|
|
77
|
+
this.enableLogging();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Close active connections.
|
|
82
|
+
*/
|
|
83
|
+
public close() {
|
|
84
|
+
this.conn.close();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Expose event system of Nats client.
|
|
89
|
+
*
|
|
90
|
+
* @param event
|
|
91
|
+
* @callback cb
|
|
92
|
+
*/
|
|
93
|
+
public on(event: string, cb: (...args: any[]) => void) {
|
|
94
|
+
this.conn.on(event, cb);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Subscribes a service listener to its methods within a NATS queue.
|
|
99
|
+
*
|
|
100
|
+
* IMPORTANT: Ensure service message handlers are defined via arrow functions
|
|
101
|
+
* to ensure instance variables of the class are accessible within
|
|
102
|
+
* the scope of the handlers.
|
|
103
|
+
*
|
|
104
|
+
* @param service - Service Id of listener
|
|
105
|
+
* @param @optional {string} - Optional name of queue to subscribe
|
|
106
|
+
* @param {SubscriptionRoutes} routes - Mapping of method Ids to handlers
|
|
107
|
+
*/
|
|
108
|
+
public subscribe(serviceId: string, queue: string, routes: ISubscriptionRoutes) {
|
|
109
|
+
this.logMessage(`Subscribing handlers for service='${serviceId}', queue='${queue}'`);
|
|
110
|
+
const topic = [serviceId, '>'].join('.');
|
|
111
|
+
return this.subscribeTopic(topic, queue, routes); /* Match all topics containing Service Id */
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public subscribeBroadcast(serviceId: string, routes: ISubscriptionRoutes) {
|
|
115
|
+
const routeNames = Object.keys(routes);
|
|
116
|
+
this.logMessage(`Subscribing handlers for broadcast: [${routeNames.join(', ')}]`);
|
|
117
|
+
routeNames.forEach((r) => {
|
|
118
|
+
const topic = [BROADCAST_PREFIX, r].join('.');
|
|
119
|
+
const queue = `${serviceId}-${r}`;
|
|
120
|
+
this.subscribeTopic(topic, queue, { [r]: routes[r] });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public subscribeTopic(topicQualifier: string, queue: string | undefined, routes: ISubscriptionRoutes) {
|
|
125
|
+
const opts: NATS.SubscribeOptions = {};
|
|
126
|
+
opts.queue = queue;
|
|
127
|
+
this.conn.subscribe(topicQualifier, opts, (req, reply, subject: string) => {
|
|
128
|
+
const method = subject.substring(subject.indexOf('.') + 1);
|
|
129
|
+
const handler = routes[method];
|
|
130
|
+
this.logMessage(`Receive message: full subject=${subject}, method=${method}, replyTo=${reply} `);
|
|
131
|
+
|
|
132
|
+
handler.process(req).then(
|
|
133
|
+
(resp: Buffer) => {
|
|
134
|
+
if (reply) {
|
|
135
|
+
this.logMessage(`Publishing reply: ${reply}`);
|
|
136
|
+
this.conn.publish(reply, resp);
|
|
137
|
+
} else {
|
|
138
|
+
this.logMessage('No reply required');
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
(reason) => {
|
|
142
|
+
this.logMessage(`Handler failed. Reason= ${reason}`, false);
|
|
143
|
+
throw new NatsConnectionManager.MESSAGE_HANDLER_ERROR(reason, subject);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Publish request for service.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} serviceId - Service Id of receipient
|
|
152
|
+
* @param {string} method - Id of method to call
|
|
153
|
+
* @param {Buffer} req - Request buffer
|
|
154
|
+
* @callback cb - Reply callback
|
|
155
|
+
* @param { number} [timeout] - Register a default timeout for requests
|
|
156
|
+
*/
|
|
157
|
+
// tslint:disable-next-line:max-line-length
|
|
158
|
+
public send = (serviceId: string, method: string, req: Buffer, cb: (error, reply) => void, timeout?: number): void => {
|
|
159
|
+
|
|
160
|
+
const subject = [serviceId, method].join('.');
|
|
161
|
+
|
|
162
|
+
const msgId = this.conn.requestOne(
|
|
163
|
+
subject,
|
|
164
|
+
req,
|
|
165
|
+
{},
|
|
166
|
+
timeout == null ? this.defaultRequestTimeout : timeout,
|
|
167
|
+
(reply) => {
|
|
168
|
+
|
|
169
|
+
this.logMessage(`Sending message: ${subject} (${msgId})`);
|
|
170
|
+
|
|
171
|
+
if (reply.code && reply.code === NATS.REQ_TIMEOUT) {
|
|
172
|
+
|
|
173
|
+
this.logMessage(`Reply for (${msgId}): ${JSON.stringify(reply)}`, true);
|
|
174
|
+
|
|
175
|
+
const error = new NatsConnectionManager.TIMEOUT_ERROR({
|
|
176
|
+
message: `Timeout sending request: ${subject} (${msgId})`,
|
|
177
|
+
});
|
|
178
|
+
cb(error, null);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.logMessage(`Received reply: ${subject} (${msgId})`);
|
|
183
|
+
cb(null, reply);
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Publish a broadcast message
|
|
190
|
+
*/
|
|
191
|
+
public sendBroadcast = (messageId: string, req: Buffer, cb: (error, reply) => void): void => {
|
|
192
|
+
return this.sendAsync(BROADCAST_PREFIX, messageId, req, cb);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Publish asynchronous request for service.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} serviceId - Service Id of receipient
|
|
199
|
+
* @param {string} method - Id of method to call
|
|
200
|
+
* @param {Buffer} req - Request buffer
|
|
201
|
+
* @callback cb - Reply callback
|
|
202
|
+
*/
|
|
203
|
+
public sendAsync = (serviceId: string, method: string, req: Buffer, cb: (error, reply) => void): void => {
|
|
204
|
+
|
|
205
|
+
const subject = [serviceId, method].join('.');
|
|
206
|
+
|
|
207
|
+
this.conn.publish(
|
|
208
|
+
subject,
|
|
209
|
+
req,
|
|
210
|
+
(reply) => {
|
|
211
|
+
this.logMessage(`Queued message: ${subject}`);
|
|
212
|
+
|
|
213
|
+
// NATS always returns "undefined" for publish, since reply is N/A.
|
|
214
|
+
// Protobuf Services require _some_ kind of Type (cannot be undefined or void).
|
|
215
|
+
// thus, return an empty `<Buffer >` here, which can be marshalled to the `google.protobuf.Empty` type.
|
|
216
|
+
const empty = Buffer.from([]);
|
|
217
|
+
cb(null, empty);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Register for logged connection events
|
|
223
|
+
*/
|
|
224
|
+
private enableLogging(): void {
|
|
225
|
+
/**
|
|
226
|
+
* GENERIC ERROR LOGGING
|
|
227
|
+
*/
|
|
228
|
+
this.conn.on('error', (err) => {
|
|
229
|
+
this.logMessage(err, false);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (!this.verbose) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* VERBOSE EVENT LOGGING
|
|
238
|
+
*/
|
|
239
|
+
this.conn.on('connect', (conn) => {
|
|
240
|
+
this.logMessage(`Connection established to ${conn.currentServer.url.host}`);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
this.conn.on('disconnect', () => {
|
|
244
|
+
this.logMessage('Disconnected', false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
this.conn.on('reconnect', () => {
|
|
248
|
+
this.logMessage('Reconnected', false);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.conn.on('close', () => {
|
|
252
|
+
this.logMessage('Connection closed');
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Log a message
|
|
258
|
+
*
|
|
259
|
+
* @param msg
|
|
260
|
+
*/
|
|
261
|
+
private logMessage(msg, info = true): void {
|
|
262
|
+
if (!info || this.verbose) {
|
|
263
|
+
this.logger.info(`(${this.logPrefix}) ${msg}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export default NatsConnectionManager;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as pb from "@sellout/models/.dist/sellout-proto";
|
|
2
|
+
import { PbMessageHandler } from './PbMessageHandler';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/* Message handler wrapper method for unidirectional calls */
|
|
6
|
+
function invokeWithEmptyResponse(method) {
|
|
7
|
+
return (req: Buffer) => method(req).then(() => Promise.resolve(pb.google.protobuf.Empty.create()));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provides a wrapper around asynchronous Protobuf messages that don't return a response.
|
|
12
|
+
* Incoming buffers are decoded, the handler method is called using staticly-compiled
|
|
13
|
+
* Protobuf definitions.
|
|
14
|
+
*/
|
|
15
|
+
export class PbAsyncMessageHandler extends PbMessageHandler {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @constructor
|
|
19
|
+
* @param method - Message handler method to be invoked
|
|
20
|
+
* @param requestCls - Request class provided by Protobuf
|
|
21
|
+
*/
|
|
22
|
+
constructor(method, requestCls) {
|
|
23
|
+
super(invokeWithEmptyResponse(method), requestCls, pb.google.protobuf.Empty);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default PbAsyncMessageHandler;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Broadcast } from "@sellout/models/.dist/sellout-proto";
|
|
2
|
+
import { IConnectionManager } from '@sellout/models/.dist/interfaces';
|
|
3
|
+
import { PbServiceProxy } from './PbServiceProxy';
|
|
4
|
+
|
|
5
|
+
export default class PbBroadcastProxy extends PbServiceProxy<Broadcast.Publisher> {
|
|
6
|
+
constructor(conn: IConnectionManager) {
|
|
7
|
+
super(conn, 'BROADCAST');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public activate(svc = Broadcast.Publisher): Broadcast.Publisher {
|
|
11
|
+
return super.activate(svc, true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Provides handler logic for RPC sender. Note: use of arrow function
|
|
16
|
+
* required to maintain instance context.
|
|
17
|
+
*
|
|
18
|
+
* @param method
|
|
19
|
+
* @param requestData
|
|
20
|
+
* @param callback
|
|
21
|
+
*/
|
|
22
|
+
protected sendRpcImpl = (method, requestData, callback) => {
|
|
23
|
+
throw('PbBroadcastProxy does not support synchronous calls');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Provides handler logic for RPC sender. Note: use of arrow function
|
|
28
|
+
* required to maintain instance context.
|
|
29
|
+
*
|
|
30
|
+
* @param method
|
|
31
|
+
* @param requestData
|
|
32
|
+
* @param callback
|
|
33
|
+
*/
|
|
34
|
+
protected sendRpcImplAsync = (method, requestData, callback) => {
|
|
35
|
+
try {
|
|
36
|
+
this.conn.sendBroadcast(method.name, requestData, callback);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
callback(e, null);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides a wrapper around Protobuf messages. Incoming buffers are decoded, the
|
|
3
|
+
* handler method is called using staticly-compiled Protobuf definitions, and returned
|
|
4
|
+
* reponse objects are encoded and marshalled into Buffer.
|
|
5
|
+
*/
|
|
6
|
+
export class PbMessageHandler {
|
|
7
|
+
|
|
8
|
+
public method;
|
|
9
|
+
public requestCls;
|
|
10
|
+
public responseCls;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @constructor
|
|
14
|
+
* @param method - Message handler method to be invoked
|
|
15
|
+
* @param requestCls - Request class provided by Protobuf
|
|
16
|
+
* @param responseCls - Response class provided by Protobuf
|
|
17
|
+
*/
|
|
18
|
+
constructor(method, requestCls, responseCls) {
|
|
19
|
+
this.method = method;
|
|
20
|
+
this.requestCls = requestCls;
|
|
21
|
+
this.responseCls = responseCls;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Invoke registered message handler.
|
|
26
|
+
*
|
|
27
|
+
* @param {Buffer} req - Incoming request
|
|
28
|
+
* @returns {Promise<Buffer>} - Promise for buffer containing encoded response object
|
|
29
|
+
*/
|
|
30
|
+
public process(req: Buffer): Promise<Buffer> {
|
|
31
|
+
return new Promise<Buffer>((resolve, reject) => {
|
|
32
|
+
const decoded = this.requestCls.decode(req);
|
|
33
|
+
// Pass plain JS object. I think this is a good idea?
|
|
34
|
+
// Idk about the performance but we'll see :)
|
|
35
|
+
// TODO: enable defaults when decoding messages
|
|
36
|
+
const reqObj = this.requestCls.toObject(decoded);
|
|
37
|
+
this.method(reqObj).then((respObj) => {
|
|
38
|
+
const respEncoded = this.responseCls.encode(respObj).finish();
|
|
39
|
+
resolve(respEncoded);
|
|
40
|
+
})
|
|
41
|
+
.catch((e) => {
|
|
42
|
+
console.log(`Error processing message for method = ${this.method}`);
|
|
43
|
+
reject(e);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default PbMessageHandler;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { IConnectionManager } from "./interfaces";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provides a client proxy that wraps a Service definition generated by staticly compiled Protobuf definition.
|
|
5
|
+
*/
|
|
6
|
+
export class PbServiceProxy<T> {
|
|
7
|
+
protected conn: IConnectionManager;
|
|
8
|
+
protected serviceName: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @coonstructor
|
|
12
|
+
* @param conn - Connection manager to handle outbound requests
|
|
13
|
+
* @param serviceName
|
|
14
|
+
*/
|
|
15
|
+
public constructor(conn: IConnectionManager, serviceName: string) {
|
|
16
|
+
this.conn = conn;
|
|
17
|
+
this.serviceName = serviceName;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param svc Create and activate an instance of a service
|
|
23
|
+
*/
|
|
24
|
+
public activate(svc, async?): T {
|
|
25
|
+
if (async === true) {
|
|
26
|
+
return svc.create(this.sendRpcImplAsync);
|
|
27
|
+
} else {
|
|
28
|
+
// tslint:disable-line
|
|
29
|
+
return svc.create(this.sendRpcImpl);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Provides handler logic for RPC sender. Note: use of arrow function
|
|
35
|
+
* required to maintain instance context.
|
|
36
|
+
*
|
|
37
|
+
* @param method
|
|
38
|
+
* @param requestData
|
|
39
|
+
* @param callback
|
|
40
|
+
*/
|
|
41
|
+
protected sendRpcImpl = (method, requestData, callback) => {
|
|
42
|
+
try {
|
|
43
|
+
this.conn.send(this.serviceName, method.name, requestData, callback);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
callback(e, null);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Provides handler logic for RPC sender. Note: use of arrow function
|
|
51
|
+
* required to maintain instance context.
|
|
52
|
+
*
|
|
53
|
+
* @param method
|
|
54
|
+
* @param requestData
|
|
55
|
+
* @param callback
|
|
56
|
+
*/
|
|
57
|
+
protected sendRpcImplAsync = (method, requestData, callback) => {
|
|
58
|
+
try {
|
|
59
|
+
this.conn.sendAsync(this.serviceName, method.name, requestData, callback);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
callback(e, null);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default PbServiceProxy;
|
package/src/Segment.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ILogManager } from "./interfaces";
|
|
2
|
+
|
|
3
|
+
const Analytics = require('analytics-node');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Segment class to allow for
|
|
7
|
+
* tracking only in production
|
|
8
|
+
*/
|
|
9
|
+
export default class Segment {
|
|
10
|
+
private segment: any;
|
|
11
|
+
private logger: ILogManager
|
|
12
|
+
constructor(SEGMENT_IO_WRITE_KEY: string | undefined, logger: ILogManager) {
|
|
13
|
+
this.segment = null;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
|
|
16
|
+
if(SEGMENT_IO_WRITE_KEY) {
|
|
17
|
+
this.logger.info("Segment - Initializing...");
|
|
18
|
+
this.segment = new Analytics(SEGMENT_IO_WRITE_KEY);
|
|
19
|
+
} else {
|
|
20
|
+
this.logger.warn("Segment - No write key supplied, skipping initialization...");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
track(params) {
|
|
24
|
+
if (this.segment) {
|
|
25
|
+
this.logger.info(`Segment - Tracking event ${params.event}.`);
|
|
26
|
+
this.segment.track(params);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
identify(params) {
|
|
30
|
+
if (this.segment) {
|
|
31
|
+
this.logger.info(`Segment - Identified user.`);
|
|
32
|
+
this.segment.identify(params);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|