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.
Files changed (40) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -0
  4. package/dist/lib/index.d.ts +4 -0
  5. package/dist/lib/index.js +17 -0
  6. package/dist/lib/index.js.map +1 -0
  7. package/dist/lib/s2/messages.d.ts +342 -0
  8. package/dist/lib/s2/messages.js +330 -0
  9. package/dist/lib/s2/messages.js.map +1 -0
  10. package/dist/lib/s2/schedule.d.ts +35 -0
  11. package/dist/lib/s2/schedule.js +109 -0
  12. package/dist/lib/s2/schedule.js.map +1 -0
  13. package/dist/lib/s2/session.d.ts +138 -0
  14. package/dist/lib/s2/session.js +304 -0
  15. package/dist/lib/s2/session.js.map +1 -0
  16. package/dist/lib/transport/websocket.d.ts +58 -0
  17. package/dist/lib/transport/websocket.js +149 -0
  18. package/dist/lib/transport/websocket.js.map +1 -0
  19. package/dist/nodes/s2-cem-config/index.d.ts +3 -0
  20. package/dist/nodes/s2-cem-config/index.html +54 -0
  21. package/dist/nodes/s2-cem-config/index.js +16 -0
  22. package/dist/nodes/s2-cem-config/index.js.map +1 -0
  23. package/dist/nodes/s2-rm/icons/s2-logo.svg +14 -0
  24. package/dist/nodes/s2-rm/index.d.ts +34 -0
  25. package/dist/nodes/s2-rm/index.html +167 -0
  26. package/dist/nodes/s2-rm/index.js +655 -0
  27. package/dist/nodes/s2-rm/index.js.map +1 -0
  28. package/dist/nodes/s2-rm-config/index.d.ts +3 -0
  29. package/dist/nodes/s2-rm-config/index.html +254 -0
  30. package/dist/nodes/s2-rm-config/index.js +20 -0
  31. package/dist/nodes/s2-rm-config/index.js.map +1 -0
  32. package/dist/nodes/s2-websocket/icons/s2-logo.svg +14 -0
  33. package/dist/nodes/s2-websocket/index.d.ts +22 -0
  34. package/dist/nodes/s2-websocket/index.html +83 -0
  35. package/dist/nodes/s2-websocket/index.js +127 -0
  36. package/dist/nodes/s2-websocket/index.js.map +1 -0
  37. package/dist/types/config-nodes.d.ts +39 -0
  38. package/dist/types/config-nodes.js +3 -0
  39. package/dist/types/config-nodes.js.map +1 -0
  40. 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":"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"}
@@ -0,0 +1,3 @@
1
+ import { NodeRedApp } from '../../types/node-red';
2
+ declare const _default: (RED: NodeRedApp) => void;
3
+ export = _default;
@@ -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>