node-red-contrib-s2 0.1.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +17 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/s2/messages.d.ts +342 -0
- package/dist/lib/s2/messages.js +330 -0
- package/dist/lib/s2/messages.js.map +1 -0
- package/dist/lib/s2/schedule.d.ts +35 -0
- package/dist/lib/s2/schedule.js +109 -0
- package/dist/lib/s2/schedule.js.map +1 -0
- package/dist/lib/s2/session.d.ts +138 -0
- package/dist/lib/s2/session.js +304 -0
- package/dist/lib/s2/session.js.map +1 -0
- package/dist/lib/transport/websocket.d.ts +58 -0
- package/dist/lib/transport/websocket.js +149 -0
- package/dist/lib/transport/websocket.js.map +1 -0
- package/dist/nodes/s2-cem-config/index.d.ts +3 -0
- package/dist/nodes/s2-cem-config/index.html +54 -0
- package/dist/nodes/s2-cem-config/index.js +16 -0
- package/dist/nodes/s2-cem-config/index.js.map +1 -0
- package/dist/nodes/s2-rm/icons/s2-logo.svg +14 -0
- package/dist/nodes/s2-rm/index.d.ts +34 -0
- package/dist/nodes/s2-rm/index.html +167 -0
- package/dist/nodes/s2-rm/index.js +655 -0
- package/dist/nodes/s2-rm/index.js.map +1 -0
- package/dist/nodes/s2-rm-config/index.d.ts +3 -0
- package/dist/nodes/s2-rm-config/index.html +254 -0
- package/dist/nodes/s2-rm-config/index.js +20 -0
- package/dist/nodes/s2-rm-config/index.js.map +1 -0
- package/dist/nodes/s2-websocket/icons/s2-logo.svg +14 -0
- package/dist/nodes/s2-websocket/index.d.ts +22 -0
- package/dist/nodes/s2-websocket/index.html +83 -0
- package/dist/nodes/s2-websocket/index.js +127 -0
- package/dist/nodes/s2-websocket/index.js.map +1 -0
- package/dist/types/config-nodes.d.ts +39 -0
- package/dist/types/config-nodes.js +3 -0
- package/dist/types/config-nodes.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.S2Session = exports.State = void 0;
|
|
4
|
+
const messages_1 = require("./messages");
|
|
5
|
+
/**
|
|
6
|
+
* S2 session states.
|
|
7
|
+
*/
|
|
8
|
+
exports.State = Object.freeze({
|
|
9
|
+
HANDSHAKING: 'HANDSHAKING', // Handshake sent by RM, waiting for HandshakeResponse from CEM
|
|
10
|
+
CONNECTED: 'CONNECTED' // HandshakeResponse received, session active
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* S2Session manages the S2 protocol state for a single CEM connection.
|
|
14
|
+
*
|
|
15
|
+
* The RM (us) initiates the Handshake immediately after the CEM connects.
|
|
16
|
+
* This applies to both transports:
|
|
17
|
+
* - D-Bus (acload): CEM calls Connect on D-Bus, RM sends Handshake via Message signal
|
|
18
|
+
* - WebSocket (future): CEM opens WS connection, RM sends Handshake over the socket
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* const session = new S2Session({
|
|
22
|
+
* cemId,
|
|
23
|
+
* rmDetails,
|
|
24
|
+
* controlTypeConfig: { // optional control type configuration
|
|
25
|
+
* OMBC: {
|
|
26
|
+
* systemDescription: { operationModes: [...], transitions: [], timers: [] },
|
|
27
|
+
* status: { activeOperationModeId: 'mode-1', operationModeFactor: 1 }
|
|
28
|
+
* }
|
|
29
|
+
* },
|
|
30
|
+
* onSend: (msg) => <send S2 message object to CEM>,
|
|
31
|
+
* onStateChange: (state) => ...,
|
|
32
|
+
* onMessage: (msg) => <forward received message downstream>,
|
|
33
|
+
* onError: (err) => ...
|
|
34
|
+
* })
|
|
35
|
+
*
|
|
36
|
+
* session.start() // sends Handshake, call after Connect
|
|
37
|
+
* session.handleMessage(msg) // call for each Message from the CEM
|
|
38
|
+
* session.send(msg) // send an outbound S2 message (only when CONNECTED)
|
|
39
|
+
*/
|
|
40
|
+
class S2Session {
|
|
41
|
+
constructor({ cemId, rmDetails, controlTypeConfig, onSend, onStateChange, onMessage, onInstruction, onError, retryDelayMs, skipInstructionStatus }) {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
this._cemId = cemId;
|
|
44
|
+
this._rmDetails = rmDetails;
|
|
45
|
+
this._controlTypeConfig = controlTypeConfig || {};
|
|
46
|
+
this._onSend = onSend || (() => { });
|
|
47
|
+
this._onStateChange = onStateChange || (() => { });
|
|
48
|
+
this._onMessage = onMessage || (() => { });
|
|
49
|
+
this._onInstruction = onInstruction || this._onMessage;
|
|
50
|
+
this._onError = onError || ((err) => console.error(err));
|
|
51
|
+
this._retryDelayMs = retryDelayMs !== null && retryDelayMs !== void 0 ? retryDelayMs : 5000;
|
|
52
|
+
this._skipInstructionStatus = skipInstructionStatus === true;
|
|
53
|
+
this._sentMessages = new Map();
|
|
54
|
+
this._retryTimers = new Map();
|
|
55
|
+
this._state = exports.State.HANDSHAKING;
|
|
56
|
+
this._selectedControlType = messages_1.ControlType.NO_SELECTION;
|
|
57
|
+
this._lastKeepAlive = null;
|
|
58
|
+
this._pebcPowerConstraints = null;
|
|
59
|
+
this._currentOMBCStatus = (_b = (_a = this._controlTypeConfig.OMBC) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : null;
|
|
60
|
+
}
|
|
61
|
+
get cemId() { return this._cemId; }
|
|
62
|
+
get state() { return this._state; }
|
|
63
|
+
get selectedControlType() { return this._selectedControlType; }
|
|
64
|
+
get lastKeepAlive() { return this._lastKeepAlive; }
|
|
65
|
+
get currentOMBCStatus() { return this._currentOMBCStatus; }
|
|
66
|
+
/**
|
|
67
|
+
* Cancel pending retry timers and clear the sent-message buffer.
|
|
68
|
+
* Call when the session is no longer needed (disconnect, node close).
|
|
69
|
+
*/
|
|
70
|
+
dispose() {
|
|
71
|
+
for (const timer of this._retryTimers.values()) {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
}
|
|
74
|
+
this._retryTimers.clear();
|
|
75
|
+
this._sentMessages.clear();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Record that a keepalive was received from the CEM.
|
|
79
|
+
*/
|
|
80
|
+
keepAlive() {
|
|
81
|
+
this._lastKeepAlive = new Date();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Send the initial Handshake to the CEM. Call once after the CEM connects.
|
|
85
|
+
*/
|
|
86
|
+
start() {
|
|
87
|
+
this._send((0, messages_1.makeHandshake)(this._cemId));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Process an S2 message received from the CEM.
|
|
91
|
+
* Accepts a raw JSON string or an already-parsed object.
|
|
92
|
+
*/
|
|
93
|
+
handleMessage(raw) {
|
|
94
|
+
let msg;
|
|
95
|
+
if (raw && typeof raw === 'object') {
|
|
96
|
+
if (!raw.message_type) {
|
|
97
|
+
this._onError(new Error('S2 message missing message_type'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
msg = raw;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
msg = (0, messages_1.parse)(raw, this._onError);
|
|
104
|
+
}
|
|
105
|
+
if (!msg)
|
|
106
|
+
return;
|
|
107
|
+
switch (msg.message_type) {
|
|
108
|
+
case messages_1.MessageType.HANDSHAKE:
|
|
109
|
+
// CEM's own Handshake (role: CEM) - ack and forward as a regular message
|
|
110
|
+
this._send((0, messages_1.makeReceptionStatus)(msg.message_id, messages_1.ReceptionStatusResult.OK));
|
|
111
|
+
this._onMessage(msg);
|
|
112
|
+
break;
|
|
113
|
+
case messages_1.MessageType.HANDSHAKE_RESPONSE:
|
|
114
|
+
this._handleHandshakeResponse(msg);
|
|
115
|
+
break;
|
|
116
|
+
case messages_1.MessageType.SELECT_CONTROL_TYPE:
|
|
117
|
+
this._handleSelectControlType(msg);
|
|
118
|
+
break;
|
|
119
|
+
case messages_1.MessageType.RECEPTION_STATUS: {
|
|
120
|
+
const statusResult = msg.status;
|
|
121
|
+
const subjectId = msg.subject_message_id;
|
|
122
|
+
if (subjectId && statusResult === messages_1.ReceptionStatusResult.OK) {
|
|
123
|
+
const pendingTimer = this._retryTimers.get(subjectId);
|
|
124
|
+
if (pendingTimer) {
|
|
125
|
+
clearTimeout(pendingTimer);
|
|
126
|
+
this._retryTimers.delete(subjectId);
|
|
127
|
+
}
|
|
128
|
+
this._sentMessages.delete(subjectId);
|
|
129
|
+
}
|
|
130
|
+
else if (subjectId && statusResult === messages_1.ReceptionStatusResult.TEMPORARY_ERROR) {
|
|
131
|
+
const entry = this._sentMessages.get(subjectId);
|
|
132
|
+
if (entry && entry.retryCount < 1) {
|
|
133
|
+
entry.retryCount++;
|
|
134
|
+
const timer = setTimeout(() => {
|
|
135
|
+
this._retryTimers.delete(subjectId);
|
|
136
|
+
const e = this._sentMessages.get(subjectId);
|
|
137
|
+
if (e)
|
|
138
|
+
this._onSend(e.msg);
|
|
139
|
+
}, this._retryDelayMs);
|
|
140
|
+
this._retryTimers.set(subjectId, timer);
|
|
141
|
+
}
|
|
142
|
+
else if (entry) {
|
|
143
|
+
this._sentMessages.delete(subjectId);
|
|
144
|
+
this._onError(new Error(`Message ${subjectId} rejected with TEMPORARY_ERROR after retry`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
this._onMessage(msg);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case messages_1.MessageType.REVOKE_OBJECT:
|
|
151
|
+
this._send((0, messages_1.makeReceptionStatus)(msg.message_id, messages_1.ReceptionStatusResult.OK));
|
|
152
|
+
this._onMessage(msg);
|
|
153
|
+
break;
|
|
154
|
+
case messages_1.MessageType.INSTRUCTION:
|
|
155
|
+
case messages_1.MessageType.FRBC_INSTRUCTION:
|
|
156
|
+
case messages_1.MessageType.DDBC_INSTRUCTION:
|
|
157
|
+
case messages_1.MessageType.OMBC_INSTRUCTION:
|
|
158
|
+
case messages_1.MessageType.PEBC_INSTRUCTION:
|
|
159
|
+
case messages_1.MessageType.PPBC_SCHEDULE_INSTRUCTION:
|
|
160
|
+
case messages_1.MessageType.PPBC_START_INTERRUPTION_INSTRUCTION:
|
|
161
|
+
case messages_1.MessageType.PPBC_END_INTERRUPTION_INSTRUCTION:
|
|
162
|
+
this._ackAndForward(msg);
|
|
163
|
+
break;
|
|
164
|
+
default:
|
|
165
|
+
this._onMessage(msg);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Send an S2 message object to the CEM. Only valid when CONNECTED.
|
|
171
|
+
*/
|
|
172
|
+
send(msg) {
|
|
173
|
+
if (this._state !== exports.State.CONNECTED) {
|
|
174
|
+
this._onError(new Error(`Cannot send in state ${this._state} for CEM ${this._cemId}`));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this._send(msg);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Store PEBC power constraints and send them to the CEM immediately if
|
|
181
|
+
* PEBC is the active control type. Stored constraints are also sent
|
|
182
|
+
* automatically when SelectControlType(PEBC) is received.
|
|
183
|
+
*/
|
|
184
|
+
setPEBCPowerConstraints(input) {
|
|
185
|
+
this._pebcPowerConstraints = input;
|
|
186
|
+
if (this._state === exports.State.CONNECTED &&
|
|
187
|
+
this._selectedControlType === messages_1.ControlType.PEBC) {
|
|
188
|
+
this._send((0, messages_1.makePEBCPowerConstraints)(input));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Update the tracked OMBC status and send OMBC.Status to the CEM.
|
|
193
|
+
* Call after an OMBC.Instruction is dispatched to inform the CEM of the new
|
|
194
|
+
* active operation mode. Also updates the status used when SELECT_CONTROL_TYPE
|
|
195
|
+
* is received again (e.g. after a CEM reconnect).
|
|
196
|
+
*/
|
|
197
|
+
updateOMBCStatus(status) {
|
|
198
|
+
if (this._currentOMBCStatus &&
|
|
199
|
+
this._currentOMBCStatus.activeOperationModeId !== status.activeOperationModeId) {
|
|
200
|
+
status = {
|
|
201
|
+
...status,
|
|
202
|
+
previousOperationModeId: this._currentOMBCStatus.activeOperationModeId,
|
|
203
|
+
transitionTimestamp: new Date().toISOString()
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
this._currentOMBCStatus = status;
|
|
207
|
+
if (this._state === exports.State.CONNECTED) {
|
|
208
|
+
this._send((0, messages_1.makeOMBCStatus)(status));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Send an InstructionStatusUpdate for an instruction previously received from the CEM.
|
|
213
|
+
* Use this to report STARTED, SUCCEEDED, ABORTED, etc. after ACCEPTED was auto-sent.
|
|
214
|
+
*
|
|
215
|
+
* @param instructionId - the `id` field of the instruction (not its message_id)
|
|
216
|
+
* @param status - one of InstructionStatus values
|
|
217
|
+
*/
|
|
218
|
+
sendInstructionStatus(instructionId, status) {
|
|
219
|
+
this._send((0, messages_1.makeInstructionStatusUpdate)(instructionId, status));
|
|
220
|
+
}
|
|
221
|
+
// -- private --
|
|
222
|
+
/**
|
|
223
|
+
* Buffer the message by its message_id (if present) and forward to onSend.
|
|
224
|
+
* Buffered messages can be retried when the CEM responds with TEMPORARY_ERROR.
|
|
225
|
+
*/
|
|
226
|
+
_send(msg) {
|
|
227
|
+
const m = msg;
|
|
228
|
+
if (typeof m.message_id === 'string') {
|
|
229
|
+
this._sentMessages.set(m.message_id, { msg, retryCount: 0 });
|
|
230
|
+
}
|
|
231
|
+
this._onSend(msg);
|
|
232
|
+
}
|
|
233
|
+
_setState(newState) {
|
|
234
|
+
if (newState !== this._state) {
|
|
235
|
+
this._state = newState;
|
|
236
|
+
this._onStateChange(newState);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
_handleHandshakeResponse(msg) {
|
|
240
|
+
if (this._state !== exports.State.HANDSHAKING) {
|
|
241
|
+
this._onError(new Error(`Unexpected HandshakeResponse in state ${this._state} for CEM ${this._cemId}`));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
this._send((0, messages_1.makeReceptionStatus)(msg.message_id, messages_1.ReceptionStatusResult.OK));
|
|
245
|
+
this._setState(exports.State.CONNECTED);
|
|
246
|
+
this._onMessage(msg);
|
|
247
|
+
if (this._rmDetails) {
|
|
248
|
+
this._send((0, messages_1.makeResourceManagerDetails)(this._rmDetails));
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
this._onError(new Error(`No rmDetails configured - cannot send ResourceManagerDetails for CEM ${this._cemId}`));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
_handleSelectControlType(msg) {
|
|
255
|
+
this._selectedControlType = msg.control_type || messages_1.ControlType.NO_SELECTION;
|
|
256
|
+
this._send((0, messages_1.makeReceptionStatus)(msg.message_id, messages_1.ReceptionStatusResult.OK));
|
|
257
|
+
this._onMessage(msg);
|
|
258
|
+
// After SelectControlType, send the appropriate SystemDescription and Status
|
|
259
|
+
const controlType = msg.control_type;
|
|
260
|
+
if (controlType === messages_1.ControlType.OMBC || controlType === 'OMBC') {
|
|
261
|
+
this._sendOMBCSystemDescriptionAndStatus();
|
|
262
|
+
}
|
|
263
|
+
else if (controlType === messages_1.ControlType.PEBC) {
|
|
264
|
+
if (this._pebcPowerConstraints) {
|
|
265
|
+
this._send((0, messages_1.makePEBCPowerConstraints)(this._pebcPowerConstraints));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
_sendOMBCSystemDescriptionAndStatus() {
|
|
270
|
+
const config = this._controlTypeConfig.OMBC;
|
|
271
|
+
if (!config) {
|
|
272
|
+
this._onError(new Error(`No OMBC config for CEM ${this._cemId} - set Control Type Config in the s2-rm node`));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (config.systemDescription) {
|
|
276
|
+
this._send((0, messages_1.makeOMBCSystemDescription)(config.systemDescription));
|
|
277
|
+
}
|
|
278
|
+
if (this._currentOMBCStatus) {
|
|
279
|
+
this._send((0, messages_1.makeOMBCStatus)(this._currentOMBCStatus));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
_ackAndForward(msg) {
|
|
283
|
+
this._send((0, messages_1.makeReceptionStatus)(msg.message_id, messages_1.ReceptionStatusResult.OK));
|
|
284
|
+
const instructionId = msg.id;
|
|
285
|
+
if (typeof instructionId === 'string' && !this._skipInstructionStatus) {
|
|
286
|
+
this._send((0, messages_1.makeInstructionStatusUpdate)(instructionId, messages_1.InstructionStatus.ACCEPTED));
|
|
287
|
+
}
|
|
288
|
+
// For OMBC instructions: send OMBC.Status immediately at accept time.
|
|
289
|
+
// transition_timestamp is set to now (initiation time) by updateOMBCStatus when the mode changes.
|
|
290
|
+
if (msg.message_type === messages_1.MessageType.OMBC_INSTRUCTION) {
|
|
291
|
+
const modeId = msg.operation_mode_id;
|
|
292
|
+
const factor = msg.operation_mode_factor;
|
|
293
|
+
if (modeId) {
|
|
294
|
+
this.updateOMBCStatus({
|
|
295
|
+
activeOperationModeId: modeId,
|
|
296
|
+
operationModeFactor: typeof factor === 'number' ? factor : 1
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
this._onInstruction(msg);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
exports.S2Session = S2Session;
|
|
304
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/lib/s2/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface S2WebSocketTransportOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
reconnectInterval?: number;
|
|
5
|
+
maxReconnectInterval?: number;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* S2WebSocketTransport manages a single outbound WebSocket connection
|
|
10
|
+
* from an RM to a CEM. It is a thin EventEmitter wrapper around `ws`
|
|
11
|
+
* with automatic reconnection, exponential backoff, and heartbeat.
|
|
12
|
+
*
|
|
13
|
+
* Events:
|
|
14
|
+
* 'open' - WS connection established
|
|
15
|
+
* 'message' - raw string received from CEM, arg: string
|
|
16
|
+
* 'close' - connection closed
|
|
17
|
+
* 'error' - error occurred, arg: Error
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const transport = new S2WebSocketTransport({ url: 'wss://cem.local/s2/{resourceId}' })
|
|
21
|
+
* transport.on('open', () => { transport.send(JSON.stringify(handshake)) })
|
|
22
|
+
* transport.on('message', (raw) => handleMessage(raw))
|
|
23
|
+
* transport.on('close', () => handleDisconnect())
|
|
24
|
+
* transport.connect()
|
|
25
|
+
*/
|
|
26
|
+
export declare class S2WebSocketTransport extends EventEmitter {
|
|
27
|
+
private readonly _url;
|
|
28
|
+
private readonly _initialReconnectInterval;
|
|
29
|
+
private readonly _maxReconnectInterval;
|
|
30
|
+
private readonly _headers;
|
|
31
|
+
private _ws;
|
|
32
|
+
private _reconnectTimer;
|
|
33
|
+
private _heartbeatTimer;
|
|
34
|
+
private _intentionalClose;
|
|
35
|
+
private _currentReconnectInterval;
|
|
36
|
+
private _isAlive;
|
|
37
|
+
private _lastContact;
|
|
38
|
+
constructor({ url, reconnectInterval, maxReconnectInterval, headers }: S2WebSocketTransportOptions);
|
|
39
|
+
/**
|
|
40
|
+
* Initiate the WebSocket connection to the CEM.
|
|
41
|
+
*/
|
|
42
|
+
connect(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Close the WebSocket connection and stop reconnecting.
|
|
45
|
+
*/
|
|
46
|
+
disconnect(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Send a raw string over the WebSocket. Throws if not connected.
|
|
49
|
+
*/
|
|
50
|
+
send(raw: string): void;
|
|
51
|
+
get url(): string;
|
|
52
|
+
get lastContact(): Date | null;
|
|
53
|
+
private _recordActivity;
|
|
54
|
+
private _doConnect;
|
|
55
|
+
private _startHeartbeat;
|
|
56
|
+
private _stopHeartbeat;
|
|
57
|
+
private _scheduleReconnect;
|
|
58
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.S2WebSocketTransport = void 0;
|
|
7
|
+
const events_1 = require("events");
|
|
8
|
+
const ws_1 = __importDefault(require("ws"));
|
|
9
|
+
/**
|
|
10
|
+
* S2WebSocketTransport manages a single outbound WebSocket connection
|
|
11
|
+
* from an RM to a CEM. It is a thin EventEmitter wrapper around `ws`
|
|
12
|
+
* with automatic reconnection, exponential backoff, and heartbeat.
|
|
13
|
+
*
|
|
14
|
+
* Events:
|
|
15
|
+
* 'open' - WS connection established
|
|
16
|
+
* 'message' - raw string received from CEM, arg: string
|
|
17
|
+
* 'close' - connection closed
|
|
18
|
+
* 'error' - error occurred, arg: Error
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const transport = new S2WebSocketTransport({ url: 'wss://cem.local/s2/{resourceId}' })
|
|
22
|
+
* transport.on('open', () => { transport.send(JSON.stringify(handshake)) })
|
|
23
|
+
* transport.on('message', (raw) => handleMessage(raw))
|
|
24
|
+
* transport.on('close', () => handleDisconnect())
|
|
25
|
+
* transport.connect()
|
|
26
|
+
*/
|
|
27
|
+
class S2WebSocketTransport extends events_1.EventEmitter {
|
|
28
|
+
constructor({ url, reconnectInterval = 5000, maxReconnectInterval = 30000, headers }) {
|
|
29
|
+
super();
|
|
30
|
+
this._url = url;
|
|
31
|
+
this._initialReconnectInterval = reconnectInterval;
|
|
32
|
+
this._maxReconnectInterval = maxReconnectInterval;
|
|
33
|
+
this._headers = headers;
|
|
34
|
+
this._ws = null;
|
|
35
|
+
this._reconnectTimer = null;
|
|
36
|
+
this._heartbeatTimer = null;
|
|
37
|
+
this._intentionalClose = false;
|
|
38
|
+
this._currentReconnectInterval = reconnectInterval;
|
|
39
|
+
this._isAlive = false;
|
|
40
|
+
this._lastContact = null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Initiate the WebSocket connection to the CEM.
|
|
44
|
+
*/
|
|
45
|
+
connect() {
|
|
46
|
+
this._intentionalClose = false;
|
|
47
|
+
this._doConnect();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Close the WebSocket connection and stop reconnecting.
|
|
51
|
+
*/
|
|
52
|
+
disconnect() {
|
|
53
|
+
this._intentionalClose = true;
|
|
54
|
+
this._stopHeartbeat();
|
|
55
|
+
if (this._reconnectTimer) {
|
|
56
|
+
clearTimeout(this._reconnectTimer);
|
|
57
|
+
this._reconnectTimer = null;
|
|
58
|
+
}
|
|
59
|
+
if (this._ws) {
|
|
60
|
+
this._ws.terminate();
|
|
61
|
+
this._ws = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Send a raw string over the WebSocket. Throws if not connected.
|
|
66
|
+
*/
|
|
67
|
+
send(raw) {
|
|
68
|
+
if (!this._ws) {
|
|
69
|
+
throw new Error('WebSocket is not connected');
|
|
70
|
+
}
|
|
71
|
+
this._ws.send(raw);
|
|
72
|
+
}
|
|
73
|
+
get url() {
|
|
74
|
+
return this._url;
|
|
75
|
+
}
|
|
76
|
+
get lastContact() {
|
|
77
|
+
return this._lastContact;
|
|
78
|
+
}
|
|
79
|
+
// -- private --
|
|
80
|
+
_recordActivity() {
|
|
81
|
+
this._isAlive = true;
|
|
82
|
+
this._lastContact = new Date();
|
|
83
|
+
this.emit('activity', this._lastContact);
|
|
84
|
+
}
|
|
85
|
+
_doConnect() {
|
|
86
|
+
const ws = this._headers
|
|
87
|
+
? new ws_1.default(this._url, { headers: this._headers })
|
|
88
|
+
: new ws_1.default(this._url);
|
|
89
|
+
this._ws = ws;
|
|
90
|
+
ws.on('open', () => {
|
|
91
|
+
this._currentReconnectInterval = this._initialReconnectInterval;
|
|
92
|
+
this._recordActivity();
|
|
93
|
+
this._startHeartbeat();
|
|
94
|
+
this.emit('open');
|
|
95
|
+
});
|
|
96
|
+
ws.on('message', (data) => {
|
|
97
|
+
this._recordActivity();
|
|
98
|
+
this.emit('message', data.toString());
|
|
99
|
+
});
|
|
100
|
+
ws.on('pong', () => {
|
|
101
|
+
this._recordActivity();
|
|
102
|
+
});
|
|
103
|
+
ws.on('close', () => {
|
|
104
|
+
this._ws = null;
|
|
105
|
+
this._stopHeartbeat();
|
|
106
|
+
this.emit('close');
|
|
107
|
+
if (!this._intentionalClose) {
|
|
108
|
+
this._scheduleReconnect();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
ws.on('error', (err) => {
|
|
112
|
+
this.emit('error', err);
|
|
113
|
+
// 'close' will fire after 'error', reconnect is handled there
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
_startHeartbeat() {
|
|
117
|
+
this._stopHeartbeat();
|
|
118
|
+
this._heartbeatTimer = setInterval(() => {
|
|
119
|
+
if (this._isAlive === false) {
|
|
120
|
+
if (this._ws) {
|
|
121
|
+
this._ws.terminate();
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this._isAlive = false;
|
|
126
|
+
if (this._ws) {
|
|
127
|
+
this._ws.ping();
|
|
128
|
+
}
|
|
129
|
+
}, 30000);
|
|
130
|
+
}
|
|
131
|
+
_stopHeartbeat() {
|
|
132
|
+
if (this._heartbeatTimer) {
|
|
133
|
+
clearInterval(this._heartbeatTimer);
|
|
134
|
+
this._heartbeatTimer = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
_scheduleReconnect() {
|
|
138
|
+
if (this._reconnectTimer)
|
|
139
|
+
return;
|
|
140
|
+
this._reconnectTimer = setTimeout(() => {
|
|
141
|
+
this._reconnectTimer = null;
|
|
142
|
+
this._doConnect();
|
|
143
|
+
}, this._currentReconnectInterval);
|
|
144
|
+
// Increase interval for next time
|
|
145
|
+
this._currentReconnectInterval = Math.min(this._currentReconnectInterval * 2, this._maxReconnectInterval);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.S2WebSocketTransport = S2WebSocketTransport;
|
|
149
|
+
//# sourceMappingURL=websocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../../src/lib/transport/websocket.ts"],"names":[],"mappings":";;;;;;AAAA,mCAAqC;AACrC,4CAA0B;AAS1B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,oBAAqB,SAAQ,qBAAY;IAapD,YAAa,EAAE,GAAG,EAAE,iBAAiB,GAAG,IAAI,EAAE,oBAAoB,GAAG,KAAK,EAAE,OAAO,EAA+B;QAChH,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;QACf,IAAI,CAAC,yBAAyB,GAAG,iBAAiB,CAAA;QAClD,IAAI,CAAC,qBAAqB,GAAG,oBAAoB,CAAA;QACjD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;QACvB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAA;QACf,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC3B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC3B,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAA;QAC9B,IAAI,CAAC,yBAAyB,GAAG,iBAAiB,CAAA;QAClD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAA;QACrB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;IAC1B,CAAC;IAED;;OAEG;IACH,OAAO;QACL,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAA;QAC9B,IAAI,CAAC,UAAU,EAAE,CAAA;IACnB,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAA;QAC7B,IAAI,CAAC,cAAc,EAAE,CAAA;QACrB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;YAClC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC7B,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAA;YACpB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAA;QACjB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,IAAI,CAAE,GAAW;QACf,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;QAC/C,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACpB,CAAC;IAED,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,YAAY,CAAA;IAC1B,CAAC;IAED,gBAAgB;IAER,eAAe;QACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,EAAE,CAAA;QAC9B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;IAC1C,CAAC;IAEO,UAAU;QAChB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ;YACtB,CAAC,CAAC,IAAI,YAAS,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtD,CAAC,CAAC,IAAI,YAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC5B,IAAI,CAAC,GAAG,GAAG,EAAE,CAAA;QAEb,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACjB,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,yBAAyB,CAAA;YAC/D,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACnB,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QACvC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACjB,IAAI,CAAC,eAAe,EAAE,CAAA;QACxB,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAA;YACf,IAAI,CAAC,cAAc,EAAE,CAAA;YACrB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAClB,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC5B,IAAI,CAAC,kBAAkB,EAAE,CAAA;YAC3B,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACrB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;YACvB,8DAA8D;QAChE,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,cAAc,EAAE,CAAA;QACrB,IAAI,CAAC,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;YACtC,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;gBAC5B,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;oBACb,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAA;gBACtB,CAAC;gBACD,OAAM;YACR,CAAC;YACD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAA;YACrB,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;YACjB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAEO,cAAc;QACpB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;YACnC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,kBAAkB;QACxB,IAAI,IAAI,CAAC,eAAe;YAAE,OAAM;QAEhC,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,GAAG,EAAE;YACrC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;YAC3B,IAAI,CAAC,UAAU,EAAE,CAAA;QACnB,CAAC,EAAE,IAAI,CAAC,yBAAyB,CAAC,CAAA;QAElC,kCAAkC;QAClC,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,GAAG,CACvC,IAAI,CAAC,yBAAyB,GAAG,CAAC,EAClC,IAAI,CAAC,qBAAqB,CAC3B,CAAA;IACH,CAAC;CACF;AAxJD,oDAwJC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script src="resources/node-red-contrib-s2/s2-common.js"></script>
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
RED.nodes.registerType('s2-cem-config', {
|
|
4
|
+
category: 'config',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
url: { value: '', required: true },
|
|
8
|
+
apiPrefix: { value: '' }
|
|
9
|
+
},
|
|
10
|
+
credentials: {
|
|
11
|
+
username: { type: 'text' },
|
|
12
|
+
password: { type: 'password' }
|
|
13
|
+
},
|
|
14
|
+
label: function () {
|
|
15
|
+
return this.name || this.url || 's2-cem-config'
|
|
16
|
+
},
|
|
17
|
+
oneditprepare: function () {
|
|
18
|
+
window.__s2Common.initializeTooltips()
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<script type="text/html" data-template-name="s2-cem-config">
|
|
24
|
+
<div class="s2-form">
|
|
25
|
+
|
|
26
|
+
<div class="form-row">
|
|
27
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
28
|
+
<input type="text" id="node-config-input-name" placeholder="Name (optional)">
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="form-row">
|
|
32
|
+
<label for="node-config-input-url"><i class="fa fa-globe"></i> CEM URL<i class="fa fa-info-circle tooltip-icon" data-tooltip="WebSocket URL of the CEM. Either use {resourceId} as a placeholder (e.g. wss://cem.example.com/s2/{resourceId}) or end the URL with a trailing slash and the Resource ID will be appended automatically (e.g. wss://cem.example.com/s2/ws/)."></i></label>
|
|
33
|
+
<input type="text" id="node-config-input-url" placeholder="wss://cem.example.com/s2/ws/">
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="form-row">
|
|
37
|
+
<label for="node-config-input-apiPrefix"><i class="fa fa-folder"></i> API Prefix<i class="fa fa-info-circle tooltip-icon" data-tooltip="Optional path prefix for the CEM REST API (e.g. /s2-message-handler). Used to build flex instruction URLs. Leave empty if the REST API is at the root."></i></label>
|
|
38
|
+
<input type="text" id="node-config-input-apiPrefix" placeholder="/s2-message-handler (optional)">
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="form-row">
|
|
42
|
+
<label for="node-config-input-username"><i class="fa fa-user"></i> Username</label>
|
|
43
|
+
<input type="text" id="node-config-input-username" placeholder="Username (optional)">
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="form-row">
|
|
47
|
+
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
|
|
48
|
+
<input type="password" id="node-config-input-password" placeholder="Password (optional)">
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
</div>
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<link rel="stylesheet" href="resources/node-red-contrib-s2/s2-styles.css">
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
module.exports = function (RED) {
|
|
3
|
+
function S2CemConfigNodeConstructor(config) {
|
|
4
|
+
RED.nodes.createNode(this, config);
|
|
5
|
+
this.url = config.url;
|
|
6
|
+
this.apiPrefix = config.apiPrefix;
|
|
7
|
+
// credentials are populated automatically by Node-RED
|
|
8
|
+
}
|
|
9
|
+
RED.nodes.registerType('s2-cem-config', S2CemConfigNodeConstructor, {
|
|
10
|
+
credentials: {
|
|
11
|
+
username: { type: 'text' },
|
|
12
|
+
password: { type: 'password' }
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/nodes/s2-cem-config/index.ts"],"names":[],"mappings":";AAGA,iBAAS,UAAU,GAAe;IAChC,SAAS,0BAA0B,CAAyB,MAAkB;QAC5E,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,GAAa,CAAA;QAC/B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAA+B,CAAA;QACvD,sDAAsD;IACxD,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,0BAA0B,EAAE;QAClE,WAAW,EAAE;YACX,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;YAC1B,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE;SAC/B;KACF,CAAC,CAAA;AACJ,CAAC,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1042 1042" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
2
|
+
<g transform="matrix(1,0,0,1,-312,-142)">
|
|
3
|
+
<g transform="matrix(1038.75,-1038.75,-1038.75,-1038.75,314.531,1181.19)">
|
|
4
|
+
<path d="M0.75,0.251C0.739,0.262 0.727,0.272 0.715,0.281C0.708,0.286 0.701,0.291 0.694,0.296C0.65,0.325 0.6,0.344 0.549,0.351C0.516,0.356 0.482,0.356 0.449,0.351C0.398,0.344 0.348,0.325 0.303,0.296C0.284,0.283 0.265,0.268 0.248,0.251C0.22,0.223 0.22,0.178 0.248,0.15C0.276,0.123 0.321,0.123 0.348,0.15C0.366,0.168 0.386,0.182 0.407,0.192C0.465,0.22 0.533,0.22 0.591,0.192C0.612,0.182 0.632,0.168 0.649,0.15L0.758,0.042C0.773,0.027 0.789,0.015 0.806,0.006C0.821,-0.003 0.837,-0.009 0.853,-0.013C0.902,-0.026 0.954,-0.022 1,-0L0.75,0.251ZM0.525,0.115C0.499,0.112 0.482,0.097 0.474,0.088C0.465,0.078 0.466,0.062 0.477,0.052C0.487,0.043 0.503,0.044 0.513,0.054C0.515,0.057 0.521,0.063 0.531,0.064C0.541,0.065 0.551,0.061 0.557,0.052C0.564,0.042 0.564,0.026 0.556,0.016C0.549,0.008 0.538,0.006 0.535,0.005L0.33,-0.014L0.447,-0.132C0.457,-0.142 0.474,-0.142 0.483,-0.132C0.493,-0.122 0.493,-0.106 0.483,-0.096L0.443,-0.055L0.541,-0.045C0.551,-0.044 0.577,-0.039 0.596,-0.015C0.618,0.013 0.619,0.052 0.599,0.081C0.596,0.085 0.593,0.089 0.59,0.092C0.573,0.109 0.549,0.118 0.525,0.115ZM0.649,-0.15C0.632,-0.168 0.612,-0.182 0.591,-0.192C0.533,-0.22 0.465,-0.22 0.407,-0.192C0.386,-0.182 0.366,-0.168 0.348,-0.15L0.24,-0.042C0.225,-0.027 0.209,-0.015 0.192,-0.006C0.177,0.003 0.161,0.009 0.145,0.013C0.096,0.026 0.044,0.022 -0.003,0L0.248,-0.251C0.259,-0.262 0.271,-0.272 0.282,-0.281C0.289,-0.286 0.296,-0.291 0.303,-0.296C0.348,-0.325 0.398,-0.344 0.449,-0.351C0.482,-0.356 0.516,-0.356 0.549,-0.351C0.6,-0.344 0.65,-0.325 0.694,-0.296C0.714,-0.283 0.732,-0.268 0.75,-0.251C0.777,-0.223 0.777,-0.178 0.75,-0.15C0.722,-0.123 0.677,-0.123 0.649,-0.15Z" style="fill:url(#s2logo-gradient);fill-rule:nonzero;"/>
|
|
5
|
+
</g>
|
|
6
|
+
</g>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="s2logo-gradient" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,-1,0,3.73045e-07)">
|
|
9
|
+
<stop offset="0" style="stop-color:rgb(45,19,75);stop-opacity:1"/>
|
|
10
|
+
<stop offset="0.37" style="stop-color:rgb(25,38,101);stop-opacity:1"/>
|
|
11
|
+
<stop offset="1" style="stop-color:rgb(17,88,163);stop-opacity:1"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
</defs>
|
|
14
|
+
</svg>
|