@ledgerhq/device-management-kit-devtools-websocket-connector 1.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.
@@ -0,0 +1,221 @@
1
+ import { formatSocketMessage, parseSocketMessage, WEBSOCKET_MESSAGE_TYPES, } from "@ledgerhq/device-management-kit-devtools-websocket-common";
2
+ import WebSocket from "isomorphic-ws";
3
+ import { ReplaySubject, Subject } from "rxjs";
4
+ export class DevToolsWebSocketConnector {
5
+ /**
6
+ * STATIC METHODS
7
+ */
8
+ static instance = null;
9
+ static getInstance() {
10
+ if (!DevToolsWebSocketConnector.instance) {
11
+ DevToolsWebSocketConnector.instance = new DevToolsWebSocketConnector();
12
+ }
13
+ return DevToolsWebSocketConnector.instance;
14
+ }
15
+ static destroyInstance() {
16
+ if (DevToolsWebSocketConnector.instance) {
17
+ DevToolsWebSocketConnector.instance.destroy();
18
+ DevToolsWebSocketConnector.instance = null;
19
+ }
20
+ }
21
+ /**
22
+ * INSTANCE
23
+ */
24
+ constructor() { }
25
+ ws = null;
26
+ wsUrl = null;
27
+ messagesToSend = new ReplaySubject();
28
+ messagesFromDashboard = new Subject();
29
+ messagesToSendSubscription = null;
30
+ destroyed = false;
31
+ verbose = false;
32
+ /**
33
+ * VERBOSE LOGGING
34
+ */
35
+ setVerbose(verbose) {
36
+ this.verbose = verbose;
37
+ }
38
+ log(...args) {
39
+ if (this.verbose)
40
+ console.log(...args);
41
+ }
42
+ warn(...args) {
43
+ if (this.verbose)
44
+ console.warn(...args);
45
+ }
46
+ error(...args) {
47
+ if (this.verbose)
48
+ console.error(...args);
49
+ }
50
+ /**
51
+ * LIFE CYCLE
52
+ */
53
+ connect(params) {
54
+ this.log("[DevToolsWebSocketConnector] 🔌 Connecting to", params.url);
55
+ if (this.destroyed) {
56
+ throw new Error("Connector is destroyed");
57
+ }
58
+ if (this.ws &&
59
+ this.wsUrl === params.url &&
60
+ (this.ws.readyState === WebSocket.OPEN ||
61
+ this.ws.readyState === WebSocket.CONNECTING)) {
62
+ const mapReadyStateToText = (readyState) => {
63
+ switch (readyState) {
64
+ case WebSocket.OPEN:
65
+ return "OPEN";
66
+ case WebSocket.CLOSED:
67
+ return "CLOSED";
68
+ case WebSocket.CONNECTING:
69
+ return "CONNECTING";
70
+ case WebSocket.CLOSING:
71
+ return "CLOSING";
72
+ default:
73
+ return "UNKNOWN";
74
+ }
75
+ };
76
+ this.log("[DevToolsWebSocketConnector] Already connected to the same URL", params.url);
77
+ this.log("[DevToolsWebSocketConnector]", mapReadyStateToText(this.ws.readyState));
78
+ return this;
79
+ }
80
+ // Clean up old subscription if it exists
81
+ if (this.messagesToSendSubscription) {
82
+ this.messagesToSendSubscription.unsubscribe();
83
+ this.messagesToSendSubscription = null;
84
+ }
85
+ if (this.ws) {
86
+ this.log("[DevToolsWebSocketConnector] Already connected to different URL, closing previous connection");
87
+ this.ws.close();
88
+ }
89
+ this.log("[DevToolsWebSocketConnector] WebSocket connecting to", params.url);
90
+ this.ws = new WebSocket(params.url);
91
+ this.wsUrl = params.url;
92
+ this.ws.onopen = () => {
93
+ this.log("[DevToolsWebSocketConnector] WebSocket connected");
94
+ if (this.reconnectionTimeout) {
95
+ clearTimeout(this.reconnectionTimeout);
96
+ this.reconnectionTimeout = null;
97
+ }
98
+ this.initialize();
99
+ };
100
+ this.ws.onclose = () => {
101
+ this.log("[DevToolsWebSocketConnector] WebSocket closed");
102
+ this.ws = null;
103
+ this.wsUrl = null;
104
+ if (this.messagesToSendSubscription) {
105
+ this.messagesToSendSubscription.unsubscribe();
106
+ this.messagesToSendSubscription = null;
107
+ }
108
+ };
109
+ this.ws.onerror = (event) => {
110
+ this.warn("[DevToolsWebSocketConnector] WebSocket error", event);
111
+ if (event.target.readyState === WebSocket.CLOSED) {
112
+ this.ws = null;
113
+ this.wsUrl = null;
114
+ this.scheduleReconnect(params);
115
+ }
116
+ };
117
+ this.ws.onmessage = (event) => {
118
+ this.log("[DevToolsWebSocketConnector] WebSocket message received", event.data);
119
+ try {
120
+ const { type: websocketMessageType, payload: websocketMessagePayload } = parseSocketMessage(event.data);
121
+ if (websocketMessageType === WEBSOCKET_MESSAGE_TYPES.MESSAGE) {
122
+ try {
123
+ const parsedMessage = JSON.parse(websocketMessagePayload);
124
+ const type = parsedMessage.type;
125
+ const payload = parsedMessage.payload;
126
+ if (typeof type !== "string" || typeof payload !== "string") {
127
+ this.error("[DevToolsWebSocketConnector] Invalid message received", {
128
+ type,
129
+ payload,
130
+ });
131
+ return;
132
+ }
133
+ this.messagesFromDashboard.next({ type, payload });
134
+ }
135
+ catch (error) {
136
+ const errorMessage = error instanceof Error ? error.message : String(error);
137
+ this.error("[DevToolsWebSocketConnector] Failed to parse message payload", errorMessage);
138
+ }
139
+ }
140
+ }
141
+ catch (error) {
142
+ const errorMessage = error instanceof Error ? error.message : String(error);
143
+ this.error("[DevToolsWebSocketConnector] Failed to parse message", errorMessage);
144
+ }
145
+ };
146
+ return this;
147
+ }
148
+ reconnectionTimeout = null;
149
+ scheduleReconnect(params) {
150
+ if (this.destroyed) {
151
+ return;
152
+ }
153
+ this.log("[DevToolsWebSocketConnector] Scheduling reconnect in 5 seconds");
154
+ if (this.reconnectionTimeout) {
155
+ clearTimeout(this.reconnectionTimeout);
156
+ this.reconnectionTimeout = null;
157
+ }
158
+ this.reconnectionTimeout = setTimeout(() => {
159
+ this.log("[DevToolsWebSocketConnector] Reconnecting...");
160
+ this.connect(params);
161
+ }, 5000);
162
+ }
163
+ initialize() {
164
+ this.log("[DevToolsWebSocketConnector] Initializing...");
165
+ // WebSocket.OPEN is 1
166
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
167
+ this.ws.send(formatSocketMessage({
168
+ type: WEBSOCKET_MESSAGE_TYPES.INIT,
169
+ payload: "init message from connector",
170
+ }));
171
+ }
172
+ // Clean up old subscription if it exists
173
+ if (this.messagesToSendSubscription) {
174
+ this.messagesToSendSubscription.unsubscribe();
175
+ }
176
+ this.messagesToSendSubscription = this.messagesToSend.subscribe({
177
+ next: (message) => {
178
+ // WebSocket.OPEN is 1
179
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
180
+ this.ws.send(formatSocketMessage({
181
+ type: WEBSOCKET_MESSAGE_TYPES.MESSAGE,
182
+ payload: JSON.stringify(message),
183
+ }));
184
+ }
185
+ },
186
+ });
187
+ }
188
+ destroy() {
189
+ this.destroyed = true;
190
+ if (this.messagesToSendSubscription) {
191
+ this.messagesToSendSubscription.unsubscribe();
192
+ this.messagesToSendSubscription = null;
193
+ }
194
+ if (this.ws) {
195
+ try {
196
+ this.ws.close();
197
+ }
198
+ catch (error) {
199
+ const errorMessage = error instanceof Error ? error.message : String(error);
200
+ this.error("[DevToolsWebSocketConnector] Failed to close WebSocket", errorMessage);
201
+ }
202
+ }
203
+ this.messagesToSend.complete();
204
+ this.messagesFromDashboard.complete();
205
+ }
206
+ /**
207
+ * CONNECTOR METHODS
208
+ */
209
+ sendMessage(type, payload) {
210
+ this.messagesToSend.next({ type, payload });
211
+ }
212
+ listenToMessages(listener) {
213
+ const subscription = this.messagesFromDashboard.subscribe({
214
+ next: (message) => listener(message.type, message.payload),
215
+ });
216
+ return {
217
+ unsubscribe: () => subscription.unsubscribe(),
218
+ };
219
+ }
220
+ }
221
+ //# sourceMappingURL=DevToolsWebSocketConnector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsWebSocketConnector.js","sourceRoot":"","sources":["../../../src/DevToolsWebSocketConnector.ts"],"names":[],"mappings":"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"}