1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const utils_1 = require("../lib/utils");
|
4 | const log_1 = require("../common/log");
|
5 | const log = log_1.create('middleware-manager');
|
6 | const config_1 = require("./config");
|
7 | const accounts_1 = require("./accounts");
|
8 | const core_1 = require("./core");
|
9 | const stats_1 = require("./stats");
|
10 | const middleware_pipeline_1 = require("../lib/middleware-pipeline");
|
11 | const ilp_packet_1 = require("ilp-packet");
|
12 | const { codes, UnreachableError } = ilp_packet_1.Errors;
|
13 | const BUILTIN_MIDDLEWARES = {
|
14 | errorHandler: {
|
15 | type: 'error-handler'
|
16 | },
|
17 | rateLimit: {
|
18 | type: 'rate-limit'
|
19 | },
|
20 | maxPacketAmount: {
|
21 | type: 'max-packet-amount'
|
22 | },
|
23 | throughput: {
|
24 | type: 'throughput'
|
25 | },
|
26 | balance: {
|
27 | type: 'balance'
|
28 | },
|
29 | deduplicate: {
|
30 | type: 'deduplicate'
|
31 | },
|
32 | expire: {
|
33 | type: 'expire'
|
34 | },
|
35 | validateFulfillment: {
|
36 | type: 'validate-fulfillment'
|
37 | },
|
38 | stats: {
|
39 | type: 'stats'
|
40 | },
|
41 | alert: {
|
42 | type: 'alert'
|
43 | }
|
44 | };
|
45 | class MiddlewareManager {
|
46 | constructor(deps) {
|
47 | this.startupHandlers = new Map();
|
48 | this.teardownHandlers = new Map();
|
49 | this.outgoingDataHandlers = new Map();
|
50 | this.outgoingMoneyHandlers = new Map();
|
51 | this.started = false;
|
52 | this.config = deps(config_1.default);
|
53 | this.accounts = deps(accounts_1.default);
|
54 | this.core = deps(core_1.default);
|
55 | this.stats = deps(stats_1.default);
|
56 | const disabledMiddlewareConfig = this.config.disableMiddleware || [];
|
57 | const customMiddlewareConfig = this.config.middlewares || {};
|
58 | this.middlewares = {};
|
59 | for (const name of Object.keys(BUILTIN_MIDDLEWARES)) {
|
60 | if (disabledMiddlewareConfig.includes(name)) {
|
61 | continue;
|
62 | }
|
63 | this.middlewares[name] = this.construct(name, BUILTIN_MIDDLEWARES[name]);
|
64 | }
|
65 | for (const name of Object.keys(customMiddlewareConfig)) {
|
66 | if (this.middlewares[name]) {
|
67 | throw new Error('custom middleware has same name as built-in middleware. name=' + name);
|
68 | }
|
69 | this.middlewares[name] = this.construct(name, customMiddlewareConfig[name]);
|
70 | }
|
71 | }
|
72 | construct(name, definition) {
|
73 | const Middleware = utils_1.loadModuleOfType('middleware', definition.type);
|
74 | return new Middleware(definition.options || {}, {
|
75 | getInfo: accountId => this.accounts.getInfo(accountId),
|
76 | getOwnAddress: () => this.accounts.getOwnAddress(),
|
77 | sendData: this.sendData.bind(this),
|
78 | sendMoney: this.sendMoney.bind(this),
|
79 | stats: this.stats
|
80 | });
|
81 | }
|
82 | async setup() {
|
83 | for (const accountId of this.accounts.getAccountIds()) {
|
84 | const plugin = this.accounts.getPlugin(accountId);
|
85 | await this.addPlugin(accountId, plugin);
|
86 | }
|
87 | }
|
88 | async startup() {
|
89 | this.started = true;
|
90 | for (const handler of this.startupHandlers.values()) {
|
91 | await handler(undefined);
|
92 | }
|
93 | }
|
94 | async addPlugin(accountId, plugin) {
|
95 | const pipelines = {
|
96 | startup: new middleware_pipeline_1.default(),
|
97 | teardown: new middleware_pipeline_1.default(),
|
98 | incomingData: new middleware_pipeline_1.default(),
|
99 | incomingMoney: new middleware_pipeline_1.default(),
|
100 | outgoingData: new middleware_pipeline_1.default(),
|
101 | outgoingMoney: new middleware_pipeline_1.default()
|
102 | };
|
103 | for (const middlewareName of Object.keys(this.middlewares)) {
|
104 | const middleware = this.middlewares[middlewareName];
|
105 | try {
|
106 | await middleware.applyToPipelines(pipelines, accountId);
|
107 | }
|
108 | catch (err) {
|
109 | const errInfo = (err && typeof err === 'object' && err.stack) ? err.stack : String(err);
|
110 | log.error('failed to apply middleware to account. middlewareName=%s accountId=%s error=%s', middlewareName, accountId, errInfo);
|
111 | throw new Error('failed to apply middleware. middlewareName=' + middlewareName);
|
112 | }
|
113 | }
|
114 | const submitData = async (data) => {
|
115 | try {
|
116 | return await plugin.sendData(data);
|
117 | }
|
118 | catch (e) {
|
119 | let err = e;
|
120 | if (!err || typeof err !== 'object') {
|
121 | err = new Error('non-object thrown. value=' + e);
|
122 | }
|
123 | if (!err.ilpErrorCode) {
|
124 | err.ilpErrorCode = codes.F02_UNREACHABLE;
|
125 | }
|
126 | err.message = 'failed to send packet: ' + err.message;
|
127 | throw err;
|
128 | }
|
129 | };
|
130 | const submitMoney = plugin.sendMoney.bind(plugin);
|
131 | const startupHandler = this.createHandler(pipelines.startup, accountId, async () => { return; });
|
132 | const teardownHandler = this.createHandler(pipelines.teardown, accountId, async () => { return; });
|
133 | const outgoingDataHandler = this.createHandler(pipelines.outgoingData, accountId, submitData);
|
134 | const outgoingMoneyHandler = this.createHandler(pipelines.outgoingMoney, accountId, submitMoney);
|
135 | this.startupHandlers.set(accountId, startupHandler);
|
136 | this.teardownHandlers.set(accountId, teardownHandler);
|
137 | this.outgoingDataHandlers.set(accountId, outgoingDataHandler);
|
138 | this.outgoingMoneyHandlers.set(accountId, outgoingMoneyHandler);
|
139 | const handleData = (data) => this.core.processData(data, accountId, this.sendData.bind(this));
|
140 | const handleMoney = async () => void 0;
|
141 | const incomingDataHandler = this.createHandler(pipelines.incomingData, accountId, handleData);
|
142 | const incomingMoneyHandler = this.createHandler(pipelines.incomingMoney, accountId, handleMoney);
|
143 | plugin.registerDataHandler(incomingDataHandler);
|
144 | plugin.registerMoneyHandler(incomingMoneyHandler);
|
145 | if (this.started) {
|
146 | await startupHandler(undefined);
|
147 | }
|
148 | }
|
149 | async removePlugin(accountId, plugin) {
|
150 | plugin.deregisterDataHandler();
|
151 | plugin.deregisterMoneyHandler();
|
152 | this.startupHandlers.delete(accountId);
|
153 | const teardownHandler = this.teardownHandlers.get(accountId);
|
154 | if (teardownHandler)
|
155 | await teardownHandler(undefined);
|
156 | this.teardownHandlers.delete(accountId);
|
157 | this.outgoingDataHandlers.delete(accountId);
|
158 | this.outgoingMoneyHandlers.delete(accountId);
|
159 | }
|
160 | async sendData(data, accountId) {
|
161 | const handler = this.outgoingDataHandlers.get(accountId);
|
162 | if (!handler) {
|
163 | throw new UnreachableError('tried to send data to non-existent account. accountId=' + accountId);
|
164 | }
|
165 | return handler(data);
|
166 | }
|
167 | async sendMoney(amount, accountId) {
|
168 | const handler = this.outgoingMoneyHandlers.get(accountId);
|
169 | if (!handler) {
|
170 | throw new UnreachableError('tried to send money to non-existent account. accountId=' + accountId);
|
171 | }
|
172 | return handler(amount);
|
173 | }
|
174 | getMiddleware(name) {
|
175 | return this.middlewares[name];
|
176 | }
|
177 | createHandler(pipeline, accountId, next) {
|
178 | const middleware = utils_1.composeMiddleware(pipeline.getMethods());
|
179 | return (param) => middleware(param, next);
|
180 | }
|
181 | }
|
182 | exports.default = MiddlewareManager;
|
183 |
|
\ | No newline at end of file |