@n1xyz/nord-ts 0.3.2 → 0.3.4

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 (49) hide show
  1. package/dist/actions.js +184 -0
  2. package/dist/client/Nord.d.ts +9 -15
  3. package/dist/client/Nord.js +759 -0
  4. package/dist/client/NordAdmin.js +362 -0
  5. package/dist/client/NordUser.d.ts +3 -1
  6. package/dist/client/NordUser.js +752 -0
  7. package/dist/const.js +27 -0
  8. package/dist/error.js +51 -0
  9. package/dist/gen/nord_pb.d.ts +132 -114
  10. package/dist/gen/nord_pb.js +1068 -0
  11. package/dist/gen/openapi.d.ts +345 -72
  12. package/dist/gen/openapi.js +5 -0
  13. package/dist/index.browser.js +61342 -80207
  14. package/dist/index.common.js +59722 -87597
  15. package/dist/index.js +10 -0
  16. package/dist/nord/api/actions.d.ts +128 -0
  17. package/dist/nord/api/actions.js +396 -0
  18. package/dist/nord/api/core.d.ts +16 -0
  19. package/dist/nord/api/core.js +81 -0
  20. package/dist/nord/api/metrics.d.ts +67 -0
  21. package/dist/nord/api/metrics.js +229 -0
  22. package/dist/nord/api/triggers.d.ts +7 -0
  23. package/dist/nord/api/triggers.js +38 -0
  24. package/dist/nord/client/Nord.d.ts +387 -0
  25. package/dist/nord/client/Nord.js +747 -0
  26. package/dist/nord/client/NordAdmin.d.ts +226 -0
  27. package/dist/nord/client/NordAdmin.js +410 -0
  28. package/dist/nord/client/NordClient.d.ts +16 -0
  29. package/dist/nord/client/NordClient.js +28 -0
  30. package/dist/nord/client/NordUser.d.ts +379 -0
  31. package/dist/nord/client/NordUser.js +787 -0
  32. package/dist/nord/index.d.ts +8 -0
  33. package/dist/nord/index.js +34 -0
  34. package/dist/nord/models/Subscriber.d.ts +37 -0
  35. package/dist/nord/models/Subscriber.js +25 -0
  36. package/dist/nord/utils/NordError.d.ts +35 -0
  37. package/dist/nord/utils/NordError.js +49 -0
  38. package/dist/types.d.ts +15 -6
  39. package/dist/types.js +92 -0
  40. package/dist/utils.js +193 -0
  41. package/dist/websocket/NordWebSocketClient.d.ts +1 -0
  42. package/dist/websocket/NordWebSocketClient.js +242 -0
  43. package/dist/websocket/Subscriber.d.ts +7 -1
  44. package/dist/websocket/Subscriber.js +24 -0
  45. package/dist/websocket/events.d.ts +2 -1
  46. package/dist/websocket/events.js +1 -0
  47. package/dist/websocket/index.d.ts +1 -1
  48. package/dist/websocket/index.js +80 -0
  49. package/package.json +2 -2
@@ -0,0 +1,242 @@
1
+ import { EventEmitter } from "events";
2
+ import WebSocket from "ws";
3
+ const VALID_STREAM_TYPES = ["trades", "delta", "account"];
4
+ /**
5
+ * WebSocket client for Nord exchange
6
+ *
7
+ * This client connects to one of the specific Nord WebSocket endpoints:
8
+ *
9
+ * Each endpoint handles a specific type of data and subscriptions must match
10
+ * the endpoint type (e.g., only 'trades@BTCUSDC' subscriptions are valid on
11
+ * the /ws/trades endpoint).
12
+ */
13
+ export class NordWebSocketClient extends EventEmitter {
14
+ ws = null;
15
+ url;
16
+ reconnectAttempts = 0;
17
+ maxReconnectAttempts = 5;
18
+ reconnectDelay = 1000;
19
+ pingInterval = null;
20
+ pingTimeout = null;
21
+ isBrowser;
22
+ /**
23
+ * Create a new NordWebSocketClient
24
+ * @param url WebSocket server URL
25
+ */
26
+ constructor(url) {
27
+ super();
28
+ this.url = url;
29
+ // Check if we're in a browser environment
30
+ // The most reliable way is to check for Node.js process
31
+ this.isBrowser =
32
+ typeof process === "undefined" ||
33
+ !process.versions ||
34
+ !process.versions.node;
35
+ }
36
+ /**
37
+ * Validate stream format
38
+ * @param stream Stream identifier to validate
39
+ * @throws Error if stream format is invalid
40
+ */
41
+ validateStream(stream) {
42
+ const [type, params] = stream.split("@");
43
+ if (!type || !params) {
44
+ throw new Error(`Invalid stream format: ${stream}. Expected format: <type>@<params>`);
45
+ }
46
+ // Extract the endpoint from the URL
47
+ const urlPath = new URL(this.url).pathname;
48
+ const endpoint = urlPath.split("/").pop();
49
+ // Ensure the stream type matches the endpoint we're connected to
50
+ if (endpoint && type !== endpoint) {
51
+ throw new Error(`Stream type '${type}' doesn't match the connected endpoint '${endpoint}'`);
52
+ }
53
+ if (!VALID_STREAM_TYPES.includes(type)) {
54
+ throw new Error(`Invalid stream type: ${type}. Valid types are: ${VALID_STREAM_TYPES.join(", ")}`);
55
+ }
56
+ if (type === "account" && !/^\d+$/.test(params)) {
57
+ throw new Error(`Invalid account ID in stream: ${params}. Expected numeric ID`);
58
+ }
59
+ }
60
+ /**
61
+ * Setup WebSocket ping/pong heartbeat
62
+ */
63
+ setupHeartbeat() {
64
+ if (this.pingInterval) {
65
+ clearInterval(this.pingInterval);
66
+ }
67
+ if (this.pingTimeout) {
68
+ clearTimeout(this.pingTimeout);
69
+ }
70
+ // In browser, we rely on the browser's WebSocket implementation to handle ping/pong
71
+ if (this.isBrowser) {
72
+ return;
73
+ }
74
+ this.pingInterval = setInterval(() => {
75
+ if (this.ws && !this.isBrowser) {
76
+ // Only use ping() method in Node.js environment
77
+ this.ws.ping();
78
+ // Set timeout for pong response
79
+ this.pingTimeout = setTimeout(() => {
80
+ this.emit("error", new Error("WebSocket ping timeout"));
81
+ this.close();
82
+ this.reconnect();
83
+ }, 5000); // 5 second timeout
84
+ }
85
+ }, 30000); // Send ping every 30 seconds
86
+ }
87
+ /**
88
+ * Get the appropriate WebSocket class based on environment
89
+ */
90
+ getWebSocketClass() {
91
+ if (this.isBrowser) {
92
+ // In browser environments
93
+ if (typeof globalThis !== "undefined" && globalThis.WebSocket) {
94
+ return globalThis.WebSocket;
95
+ }
96
+ throw new Error("WebSocket is not available in this environment");
97
+ }
98
+ else {
99
+ // In Node.js
100
+ return WebSocket;
101
+ }
102
+ }
103
+ /**
104
+ * Connect to the Nord WebSocket server
105
+ */
106
+ connect() {
107
+ if (this.ws) {
108
+ return;
109
+ }
110
+ try {
111
+ const WebSocketClass = this.getWebSocketClass();
112
+ if (this.isBrowser) {
113
+ // Browser WebSocket setup
114
+ this.ws = new WebSocketClass(this.url);
115
+ this.ws.onopen = () => {
116
+ this.emit("connected");
117
+ this.reconnectAttempts = 0;
118
+ this.reconnectDelay = 1000;
119
+ };
120
+ this.ws.onmessage = (event) => {
121
+ try {
122
+ const message = JSON.parse(event.data);
123
+ this.handleMessage(message);
124
+ }
125
+ catch (error) {
126
+ this.emit("error", new Error(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`));
127
+ }
128
+ };
129
+ this.ws.onclose = (_event) => {
130
+ this.emit("disconnected");
131
+ this.reconnect();
132
+ };
133
+ this.ws.onerror = (event) => {
134
+ const errorMsg = `WebSocket error: ${event && event.type ? event.type : "unknown"}`;
135
+ this.emit("error", new Error(errorMsg));
136
+ };
137
+ }
138
+ else {
139
+ // Node.js WebSocket setup
140
+ const nodeWs = new WebSocketClass(this.url);
141
+ this.ws = nodeWs;
142
+ nodeWs.on("open", () => {
143
+ this.emit("connected");
144
+ this.reconnectAttempts = 0;
145
+ this.reconnectDelay = 1000;
146
+ this.setupHeartbeat();
147
+ });
148
+ nodeWs.on("message", (data) => {
149
+ try {
150
+ const message = JSON.parse(data.toString());
151
+ this.handleMessage(message);
152
+ }
153
+ catch (error) {
154
+ this.emit("error", new Error(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`));
155
+ }
156
+ });
157
+ nodeWs.on("close", (_code, _reason) => {
158
+ this.emit("disconnected");
159
+ if (this.pingInterval) {
160
+ clearInterval(this.pingInterval);
161
+ }
162
+ if (this.pingTimeout) {
163
+ clearTimeout(this.pingTimeout);
164
+ }
165
+ this.reconnect();
166
+ });
167
+ nodeWs.on("error", (error) => {
168
+ this.emit("error", error);
169
+ });
170
+ nodeWs.on("pong", () => {
171
+ if (this.pingTimeout) {
172
+ clearTimeout(this.pingTimeout);
173
+ }
174
+ });
175
+ }
176
+ }
177
+ catch (error) {
178
+ const errorMsg = `Failed to initialize WebSocket: ${error instanceof Error ? error.message : String(error)}`;
179
+ this.emit("error", new Error(errorMsg));
180
+ }
181
+ }
182
+ /**
183
+ * Close the WebSocket connection
184
+ */
185
+ close() {
186
+ if (this.ws) {
187
+ if (this.isBrowser) {
188
+ this.ws.close();
189
+ }
190
+ else {
191
+ this.ws.close();
192
+ }
193
+ this.ws = null;
194
+ }
195
+ if (this.pingInterval) {
196
+ clearInterval(this.pingInterval);
197
+ this.pingInterval = null;
198
+ }
199
+ if (this.pingTimeout) {
200
+ clearTimeout(this.pingTimeout);
201
+ this.pingTimeout = null;
202
+ }
203
+ }
204
+ /**
205
+ * Handle incoming WebSocket messages
206
+ * @param message WebSocket message
207
+ */
208
+ handleMessage(message) {
209
+ if (!message || typeof message !== "object") {
210
+ this.emit("error", new Error(`Unexpected message type: ${message}`));
211
+ return;
212
+ }
213
+ const hasOwn = (k) => Object.prototype.hasOwnProperty.call(message, k);
214
+ if (hasOwn("trades")) {
215
+ this.emit("trades", message.trades);
216
+ return;
217
+ }
218
+ if (hasOwn("delta")) {
219
+ this.emit("delta", message.delta);
220
+ return;
221
+ }
222
+ if (hasOwn("account")) {
223
+ this.emit("account", message.account);
224
+ return;
225
+ }
226
+ this.emit("error", new Error(`Unexpected message type: ${message}`));
227
+ }
228
+ /**
229
+ * Attempt to reconnect to the WebSocket server
230
+ */
231
+ reconnect() {
232
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
233
+ this.emit("error", new Error("Max reconnection attempts reached"));
234
+ return;
235
+ }
236
+ setTimeout(() => {
237
+ this.reconnectAttempts++;
238
+ this.reconnectDelay *= 2; // Exponential backoff
239
+ this.connect();
240
+ }, this.reconnectDelay);
241
+ }
242
+ }
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from "events";
2
- import { Account, DeltaEvent, WebSocketDeltaUpdate, SubscriberConfig, Trades, WebSocketTradeUpdate } from "../types";
2
+ import { Account, DeltaEvent, WebSocketDeltaUpdate, SubscriberConfig, Trades, WebSocketTradeUpdate, WebSocketCandleUpdate } from "../types";
3
3
  /**
4
4
  * Subscriber class for handling WebSocket subscriptions
5
5
  */
@@ -35,3 +35,9 @@ export interface TradeSubscription extends EventEmitter {
35
35
  close(): void;
36
36
  removeAllListeners(event?: string): this;
37
37
  }
38
+ export interface CandleSubscription extends EventEmitter {
39
+ on(event: "message", listener: (data: WebSocketCandleUpdate) => void): this;
40
+ on(event: "error", listener: (error: Error) => void): this;
41
+ close(): void;
42
+ removeAllListeners(event?: string): this;
43
+ }
@@ -0,0 +1,24 @@
1
+ import { MAX_BUFFER_LEN } from "../utils";
2
+ /**
3
+ * Subscriber class for handling WebSocket subscriptions
4
+ */
5
+ export class Subscriber {
6
+ streamURL;
7
+ buffer;
8
+ maxBufferLen;
9
+ /**
10
+ * Create a new Subscriber instance
11
+ * @param config Subscriber configuration
12
+ */
13
+ constructor(config) {
14
+ this.streamURL = config.streamURL;
15
+ this.buffer = [];
16
+ this.maxBufferLen = config.maxBufferLen ?? MAX_BUFFER_LEN;
17
+ }
18
+ /**
19
+ * Subscribe to WebSocket events
20
+ */
21
+ subscribe() {
22
+ // TODO: Implement subscription logic
23
+ }
24
+ }
@@ -1,4 +1,4 @@
1
- import { WebSocketTradeUpdate, WebSocketDeltaUpdate, WebSocketAccountUpdate } from "../types";
1
+ import { WebSocketTradeUpdate, WebSocketDeltaUpdate, WebSocketAccountUpdate, WebSocketCandleUpdate } from "../types";
2
2
  /**
3
3
  * Event type definitions for the NordWebSocketClient
4
4
  */
@@ -9,6 +9,7 @@ export interface NordWebSocketEvents {
9
9
  trade: (update: WebSocketTradeUpdate) => void;
10
10
  delta: (update: WebSocketDeltaUpdate) => void;
11
11
  account: (update: WebSocketAccountUpdate) => void;
12
+ candle: (update: WebSocketCandleUpdate) => void;
12
13
  }
13
14
  /**
14
15
  * Type declaration for NordWebSocketClient event methods
@@ -0,0 +1 @@
1
+ export {};
@@ -16,4 +16,4 @@ export { NordWebSocketClient, NordWebSocketEvents, NordWebSocketClientEvents, Su
16
16
  * @returns WebSocket client
17
17
  * @throws {NordError} If initialization fails or invalid subscription is provided
18
18
  */
19
- export declare function initWebSocketClient(webServerUrl: string, subscriptions?: SubscriptionPattern[] | "trades" | "delta" | "account"): NordWebSocketClient;
19
+ export declare function initWebSocketClient(webServerUrl: string, subscriptions?: SubscriptionPattern[] | "trades" | "delta" | "account" | "candle"): NordWebSocketClient;
@@ -0,0 +1,80 @@
1
+ import { NordWebSocketClient } from "./NordWebSocketClient";
2
+ import { NordError } from "../error";
3
+ import { Subscriber } from "./Subscriber";
4
+ export { NordWebSocketClient, Subscriber, };
5
+ /**
6
+ * Initialize a WebSocket client for Nord
7
+ *
8
+ * Connects to the Nord WebSocket endpoint with support for multiple subscription types:
9
+ * - trades@SYMBOL - For trade updates
10
+ * - deltas@SYMBOL - For orderbook delta updates
11
+ * - account@ACCOUNT_ID - For user-specific updates
12
+ *
13
+ * @param webServerUrl - Base URL for the Nord web server
14
+ * @param subscriptions - Array of subscriptions (e.g., ["trades@BTCUSDC", "deltas@BTCUSDC", "account@42"])
15
+ * @returns WebSocket client
16
+ * @throws {NordError} If initialization fails or invalid subscription is provided
17
+ */
18
+ export function initWebSocketClient(webServerUrl, subscriptions) {
19
+ try {
20
+ // Determine URL and subscriptions based on parameters
21
+ let wsUrl = webServerUrl.replace(/^http/, "ws") + `/ws`;
22
+ // Validate subscriptions parameter
23
+ if (typeof subscriptions === "string") {
24
+ // Legacy mode - handle endpoint string
25
+ if (subscriptions === "trades" ||
26
+ subscriptions === "delta" ||
27
+ subscriptions === "account") {
28
+ wsUrl += `/${subscriptions}`;
29
+ }
30
+ else {
31
+ throw new NordError(`Invalid endpoint: ${subscriptions}. Must be "trades", "deltas", or "account".`);
32
+ }
33
+ }
34
+ else if (Array.isArray(subscriptions) && subscriptions.length > 0) {
35
+ // New mode - validate and combine subscriptions in URL
36
+ subscriptions.forEach(validateSubscription);
37
+ wsUrl += `/${subscriptions.join("&")}`;
38
+ }
39
+ else {
40
+ // Default to trades endpoint if no subscriptions specified
41
+ wsUrl += `/trades`;
42
+ }
43
+ console.log(`Initializing WebSocket client with URL: ${wsUrl}`);
44
+ // Create and connect the WebSocket client
45
+ const ws = new NordWebSocketClient(wsUrl);
46
+ // Add error handler
47
+ ws.on("error", (error) => {
48
+ console.error("Nord WebSocket error:", error);
49
+ });
50
+ // Add connected handler for debugging
51
+ ws.on("connected", () => {
52
+ console.log("Nord WebSocket connected successfully");
53
+ });
54
+ // Connect the WebSocket
55
+ ws.connect();
56
+ return ws;
57
+ }
58
+ catch (error) {
59
+ console.error("Failed to initialize WebSocket client:", error);
60
+ throw new NordError("Failed to initialize WebSocket client", {
61
+ cause: error,
62
+ });
63
+ }
64
+ }
65
+ /**
66
+ * Validates a subscription string follows the correct format
67
+ *
68
+ * @param subscription - The subscription to validate
69
+ * @throws {NordError} If the subscription format is invalid
70
+ */
71
+ function validateSubscription(subscription) {
72
+ const [type, param] = subscription.split("@");
73
+ if (!type || !param || !["trades", "deltas", "account"].includes(type)) {
74
+ throw new NordError(`Invalid subscription format: ${subscription}. Expected format: "trades@SYMBOL", "deltas@SYMBOL", or "account@ID"`);
75
+ }
76
+ // Additional validation for account subscriptions
77
+ if (type === "account" && isNaN(Number(param))) {
78
+ throw new NordError(`Invalid account ID in subscription: ${subscription}. Account ID must be a number.`);
79
+ }
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@n1xyz/nord-ts",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Typescript for Nord",
5
5
  "keywords": [],
6
6
  "author": "",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@bufbuild/protobuf": "^2.6.3",
50
- "@n1xyz/proton": "0.1.1",
50
+ "@n1xyz/proton": "0.1.0",
51
51
  "@noble/curves": "^1.9.6",
52
52
  "@noble/ed25519": "^2.3.0",
53
53
  "@noble/hashes": "^1.8.0",