@newyorkcompute/kalshi-core 0.1.1 → 0.3.0

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 (59) hide show
  1. package/dist/cache.d.ts +71 -0
  2. package/dist/cache.d.ts.map +1 -0
  3. package/dist/cache.js +96 -0
  4. package/dist/cache.js.map +1 -0
  5. package/dist/cache.test.d.ts +5 -0
  6. package/dist/cache.test.d.ts.map +1 -0
  7. package/dist/cache.test.js +108 -0
  8. package/dist/cache.test.js.map +1 -0
  9. package/dist/config.d.ts +61 -6
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +56 -5
  12. package/dist/config.js.map +1 -1
  13. package/dist/config.test.d.ts +2 -0
  14. package/dist/config.test.d.ts.map +1 -0
  15. package/dist/config.test.js +94 -0
  16. package/dist/config.test.js.map +1 -0
  17. package/dist/format.d.ts +110 -2
  18. package/dist/format.d.ts.map +1 -1
  19. package/dist/format.js +151 -2
  20. package/dist/format.js.map +1 -1
  21. package/dist/format.test.js +107 -2
  22. package/dist/format.test.js.map +1 -1
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +7 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/rate-limiter.d.ts +80 -0
  28. package/dist/rate-limiter.d.ts.map +1 -0
  29. package/dist/rate-limiter.js +180 -0
  30. package/dist/rate-limiter.js.map +1 -0
  31. package/dist/rate-limiter.test.d.ts +5 -0
  32. package/dist/rate-limiter.test.d.ts.map +1 -0
  33. package/dist/rate-limiter.test.js +175 -0
  34. package/dist/rate-limiter.test.js.map +1 -0
  35. package/dist/websocket/auth.d.ts +28 -0
  36. package/dist/websocket/auth.d.ts.map +1 -0
  37. package/dist/websocket/auth.js +55 -0
  38. package/dist/websocket/auth.js.map +1 -0
  39. package/dist/websocket/auth.test.d.ts +2 -0
  40. package/dist/websocket/auth.test.d.ts.map +1 -0
  41. package/dist/websocket/auth.test.js +56 -0
  42. package/dist/websocket/auth.test.js.map +1 -0
  43. package/dist/websocket/client.d.ts +95 -0
  44. package/dist/websocket/client.d.ts.map +1 -0
  45. package/dist/websocket/client.js +352 -0
  46. package/dist/websocket/client.js.map +1 -0
  47. package/dist/websocket/index.d.ts +9 -0
  48. package/dist/websocket/index.d.ts.map +1 -0
  49. package/dist/websocket/index.js +9 -0
  50. package/dist/websocket/index.js.map +1 -0
  51. package/dist/websocket/types.d.ts +160 -0
  52. package/dist/websocket/types.d.ts.map +1 -0
  53. package/dist/websocket/types.js +12 -0
  54. package/dist/websocket/types.js.map +1 -0
  55. package/dist/websocket/types.test.d.ts +2 -0
  56. package/dist/websocket/types.test.d.ts.map +1 -0
  57. package/dist/websocket/types.test.js +17 -0
  58. package/dist/websocket/types.test.js.map +1 -0
  59. package/package.json +8 -4
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Rate Limiter
3
+ *
4
+ * Implements exponential backoff and circuit breaker patterns
5
+ * for handling API rate limits gracefully.
6
+ */
7
+ const DEFAULT_CONFIG = {
8
+ minInterval: 1000,
9
+ maxInterval: 60000,
10
+ backoffMultiplier: 2,
11
+ circuitBreakerThreshold: 5,
12
+ circuitResetTimeout: 30000,
13
+ };
14
+ /**
15
+ * Create a rate limiter instance
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const limiter = createRateLimiter();
20
+ *
21
+ * async function fetchData() {
22
+ * if (limiter.shouldBlock()) {
23
+ * return; // Skip request
24
+ * }
25
+ *
26
+ * try {
27
+ * const data = await api.getData();
28
+ * limiter.recordSuccess();
29
+ * return data;
30
+ * } catch (err) {
31
+ * if (isRateLimitError(err)) {
32
+ * limiter.recordRateLimitError();
33
+ * } else {
34
+ * limiter.recordFailure();
35
+ * }
36
+ * throw err;
37
+ * }
38
+ * }
39
+ * ```
40
+ */
41
+ export function createRateLimiter(config = {}) {
42
+ const cfg = { ...DEFAULT_CONFIG, ...config };
43
+ let state = {
44
+ currentInterval: cfg.minInterval,
45
+ consecutiveFailures: 0,
46
+ isCircuitOpen: false,
47
+ isRateLimited: false,
48
+ lastSuccessTime: 0,
49
+ lastFailureTime: 0,
50
+ };
51
+ let circuitResetTimer = null;
52
+ /**
53
+ * Check if requests should be blocked
54
+ */
55
+ function shouldBlock() {
56
+ return state.isCircuitOpen;
57
+ }
58
+ /**
59
+ * Get the current recommended interval between requests
60
+ */
61
+ function getCurrentInterval() {
62
+ return state.currentInterval;
63
+ }
64
+ /**
65
+ * Get the current state (for debugging/display)
66
+ */
67
+ function getState() {
68
+ return { ...state };
69
+ }
70
+ /**
71
+ * Record a successful request - resets backoff
72
+ */
73
+ function recordSuccess() {
74
+ state.consecutiveFailures = 0;
75
+ state.currentInterval = cfg.minInterval;
76
+ state.isRateLimited = false;
77
+ state.lastSuccessTime = Date.now();
78
+ // Close circuit if it was open
79
+ if (state.isCircuitOpen) {
80
+ state.isCircuitOpen = false;
81
+ if (circuitResetTimer) {
82
+ clearTimeout(circuitResetTimer);
83
+ circuitResetTimer = null;
84
+ }
85
+ }
86
+ }
87
+ /**
88
+ * Record a rate limit error (429) - applies exponential backoff
89
+ */
90
+ function recordRateLimitError() {
91
+ state.consecutiveFailures++;
92
+ state.isRateLimited = true;
93
+ state.lastFailureTime = Date.now();
94
+ // Apply exponential backoff
95
+ state.currentInterval = Math.min(state.currentInterval * cfg.backoffMultiplier, cfg.maxInterval);
96
+ // Open circuit if threshold reached
97
+ if (state.consecutiveFailures >= cfg.circuitBreakerThreshold) {
98
+ openCircuit();
99
+ }
100
+ }
101
+ /**
102
+ * Record a general failure (not rate limit)
103
+ */
104
+ function recordFailure() {
105
+ state.consecutiveFailures++;
106
+ state.lastFailureTime = Date.now();
107
+ // Open circuit if threshold reached
108
+ if (state.consecutiveFailures >= cfg.circuitBreakerThreshold) {
109
+ openCircuit();
110
+ }
111
+ }
112
+ /**
113
+ * Open the circuit breaker (block all requests temporarily)
114
+ */
115
+ function openCircuit() {
116
+ if (state.isCircuitOpen)
117
+ return;
118
+ state.isCircuitOpen = true;
119
+ // Schedule circuit reset
120
+ circuitResetTimer = setTimeout(() => {
121
+ state.isCircuitOpen = false;
122
+ state.consecutiveFailures = 0;
123
+ state.currentInterval = cfg.minInterval;
124
+ circuitResetTimer = null;
125
+ }, cfg.circuitResetTimeout);
126
+ }
127
+ /**
128
+ * Reset the rate limiter to initial state
129
+ */
130
+ function reset() {
131
+ if (circuitResetTimer) {
132
+ clearTimeout(circuitResetTimer);
133
+ circuitResetTimer = null;
134
+ }
135
+ state = {
136
+ currentInterval: cfg.minInterval,
137
+ consecutiveFailures: 0,
138
+ isCircuitOpen: false,
139
+ isRateLimited: false,
140
+ lastSuccessTime: 0,
141
+ lastFailureTime: 0,
142
+ };
143
+ }
144
+ return {
145
+ shouldBlock,
146
+ getCurrentInterval,
147
+ getState,
148
+ recordSuccess,
149
+ recordRateLimitError,
150
+ recordFailure,
151
+ reset,
152
+ };
153
+ }
154
+ /**
155
+ * Check if an error is a rate limit error (HTTP 429)
156
+ *
157
+ * @param error - Error to check
158
+ * @returns true if the error is a rate limit error
159
+ */
160
+ export function isRateLimitError(error) {
161
+ if (!error || typeof error !== 'object')
162
+ return false;
163
+ // Check for status code 429
164
+ if ('status' in error && error.status === 429)
165
+ return true;
166
+ if ('response' in error) {
167
+ const response = error.response;
168
+ if (response?.status === 429)
169
+ return true;
170
+ }
171
+ // Check error message
172
+ if ('message' in error && typeof error.message === 'string') {
173
+ const msg = error.message.toLowerCase();
174
+ if (msg.includes('429') || msg.includes('rate limit') || msg.includes('too many requests')) {
175
+ return true;
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Rate Limiter Tests
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=rate-limiter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.test.d.ts","sourceRoot":"","sources":["../src/rate-limiter.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Rate Limiter Tests
3
+ */
4
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
5
+ import { createRateLimiter, isRateLimitError, } from './rate-limiter.js';
6
+ describe('createRateLimiter', () => {
7
+ beforeEach(() => {
8
+ vi.useFakeTimers();
9
+ });
10
+ afterEach(() => {
11
+ vi.useRealTimers();
12
+ });
13
+ describe('initial state', () => {
14
+ it('should not block requests initially', () => {
15
+ const limiter = createRateLimiter();
16
+ expect(limiter.shouldBlock()).toBe(false);
17
+ });
18
+ it('should have minimum interval initially', () => {
19
+ const limiter = createRateLimiter({ minInterval: 1000 });
20
+ expect(limiter.getCurrentInterval()).toBe(1000);
21
+ });
22
+ it('should have correct initial state', () => {
23
+ const limiter = createRateLimiter();
24
+ const state = limiter.getState();
25
+ expect(state.consecutiveFailures).toBe(0);
26
+ expect(state.isCircuitOpen).toBe(false);
27
+ expect(state.isRateLimited).toBe(false);
28
+ });
29
+ });
30
+ describe('recordSuccess', () => {
31
+ it('should reset consecutive failures', () => {
32
+ const limiter = createRateLimiter();
33
+ limiter.recordFailure();
34
+ limiter.recordFailure();
35
+ expect(limiter.getState().consecutiveFailures).toBe(2);
36
+ limiter.recordSuccess();
37
+ expect(limiter.getState().consecutiveFailures).toBe(0);
38
+ });
39
+ it('should reset interval to minimum', () => {
40
+ const limiter = createRateLimiter({ minInterval: 1000 });
41
+ limiter.recordRateLimitError();
42
+ expect(limiter.getCurrentInterval()).toBeGreaterThan(1000);
43
+ limiter.recordSuccess();
44
+ expect(limiter.getCurrentInterval()).toBe(1000);
45
+ });
46
+ it('should clear rate limited flag', () => {
47
+ const limiter = createRateLimiter();
48
+ limiter.recordRateLimitError();
49
+ expect(limiter.getState().isRateLimited).toBe(true);
50
+ limiter.recordSuccess();
51
+ expect(limiter.getState().isRateLimited).toBe(false);
52
+ });
53
+ });
54
+ describe('recordRateLimitError', () => {
55
+ it('should increase interval with exponential backoff', () => {
56
+ const limiter = createRateLimiter({
57
+ minInterval: 1000,
58
+ backoffMultiplier: 2,
59
+ });
60
+ limiter.recordRateLimitError();
61
+ expect(limiter.getCurrentInterval()).toBe(2000);
62
+ limiter.recordRateLimitError();
63
+ expect(limiter.getCurrentInterval()).toBe(4000);
64
+ limiter.recordRateLimitError();
65
+ expect(limiter.getCurrentInterval()).toBe(8000);
66
+ });
67
+ it('should not exceed max interval', () => {
68
+ const limiter = createRateLimiter({
69
+ minInterval: 1000,
70
+ maxInterval: 5000,
71
+ backoffMultiplier: 2,
72
+ });
73
+ // 1000 -> 2000 -> 4000 -> 5000 (capped)
74
+ limiter.recordRateLimitError();
75
+ limiter.recordRateLimitError();
76
+ limiter.recordRateLimitError();
77
+ limiter.recordRateLimitError();
78
+ expect(limiter.getCurrentInterval()).toBe(5000);
79
+ });
80
+ it('should set rate limited flag', () => {
81
+ const limiter = createRateLimiter();
82
+ limiter.recordRateLimitError();
83
+ expect(limiter.getState().isRateLimited).toBe(true);
84
+ });
85
+ it('should increment consecutive failures', () => {
86
+ const limiter = createRateLimiter();
87
+ limiter.recordRateLimitError();
88
+ expect(limiter.getState().consecutiveFailures).toBe(1);
89
+ limiter.recordRateLimitError();
90
+ expect(limiter.getState().consecutiveFailures).toBe(2);
91
+ });
92
+ });
93
+ describe('circuit breaker', () => {
94
+ it('should open circuit after threshold failures', () => {
95
+ const limiter = createRateLimiter({
96
+ circuitBreakerThreshold: 3,
97
+ });
98
+ limiter.recordFailure();
99
+ limiter.recordFailure();
100
+ expect(limiter.shouldBlock()).toBe(false);
101
+ limiter.recordFailure();
102
+ expect(limiter.shouldBlock()).toBe(true);
103
+ });
104
+ it('should reset circuit after timeout', () => {
105
+ const limiter = createRateLimiter({
106
+ circuitBreakerThreshold: 2,
107
+ circuitResetTimeout: 5000,
108
+ });
109
+ limiter.recordFailure();
110
+ limiter.recordFailure();
111
+ expect(limiter.shouldBlock()).toBe(true);
112
+ // Advance time past reset timeout
113
+ vi.advanceTimersByTime(5001);
114
+ expect(limiter.shouldBlock()).toBe(false);
115
+ expect(limiter.getState().consecutiveFailures).toBe(0);
116
+ });
117
+ it('should close circuit on success', () => {
118
+ const limiter = createRateLimiter({
119
+ circuitBreakerThreshold: 2,
120
+ });
121
+ limiter.recordFailure();
122
+ limiter.recordFailure();
123
+ expect(limiter.shouldBlock()).toBe(true);
124
+ limiter.recordSuccess();
125
+ expect(limiter.shouldBlock()).toBe(false);
126
+ });
127
+ });
128
+ describe('reset', () => {
129
+ it('should reset all state', () => {
130
+ const limiter = createRateLimiter({
131
+ minInterval: 1000,
132
+ circuitBreakerThreshold: 2,
133
+ });
134
+ limiter.recordRateLimitError();
135
+ limiter.recordRateLimitError();
136
+ expect(limiter.shouldBlock()).toBe(true);
137
+ expect(limiter.getCurrentInterval()).toBeGreaterThan(1000);
138
+ limiter.reset();
139
+ expect(limiter.shouldBlock()).toBe(false);
140
+ expect(limiter.getCurrentInterval()).toBe(1000);
141
+ expect(limiter.getState().consecutiveFailures).toBe(0);
142
+ expect(limiter.getState().isRateLimited).toBe(false);
143
+ });
144
+ });
145
+ });
146
+ describe('isRateLimitError', () => {
147
+ it('should return true for error with status 429', () => {
148
+ expect(isRateLimitError({ status: 429 })).toBe(true);
149
+ });
150
+ it('should return true for error with response.status 429', () => {
151
+ expect(isRateLimitError({ response: { status: 429 } })).toBe(true);
152
+ });
153
+ it('should return true for error message containing 429', () => {
154
+ expect(isRateLimitError({ message: 'Error 429: Too Many Requests' })).toBe(true);
155
+ });
156
+ it('should return true for error message containing rate limit', () => {
157
+ expect(isRateLimitError({ message: 'Rate limit exceeded' })).toBe(true);
158
+ });
159
+ it('should return true for error message containing too many requests', () => {
160
+ expect(isRateLimitError({ message: 'too many requests' })).toBe(true);
161
+ });
162
+ it('should return false for other errors', () => {
163
+ expect(isRateLimitError({ status: 500 })).toBe(false);
164
+ expect(isRateLimitError({ message: 'Internal server error' })).toBe(false);
165
+ });
166
+ it('should return false for null/undefined', () => {
167
+ expect(isRateLimitError(null)).toBe(false);
168
+ expect(isRateLimitError(undefined)).toBe(false);
169
+ });
170
+ it('should return false for non-objects', () => {
171
+ expect(isRateLimitError('error')).toBe(false);
172
+ expect(isRateLimitError(123)).toBe(false);
173
+ });
174
+ });
175
+ //# sourceMappingURL=rate-limiter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.test.js","sourceRoot":"","sources":["../src/rate-limiter.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Kalshi WebSocket Authentication
3
+ *
4
+ * Handles signature generation for authenticated WebSocket connections.
5
+ * Uses the same RSA-PSS signing as the REST API.
6
+ */
7
+ /**
8
+ * Generate authentication headers for WebSocket connection
9
+ *
10
+ * @param apiKeyId - Your Kalshi API key ID
11
+ * @param privateKey - Your private key in PEM format
12
+ * @param timestamp - Unix timestamp in milliseconds
13
+ * @returns Headers object for WebSocket connection
14
+ */
15
+ export declare function generateWsAuthHeaders(apiKeyId: string, privateKey: string, timestamp?: number): Record<string, string>;
16
+ /**
17
+ * Generate a signed WebSocket URL with auth params
18
+ *
19
+ * Some WebSocket implementations don't support custom headers,
20
+ * so we can pass auth as query parameters instead.
21
+ *
22
+ * @param baseUrl - WebSocket endpoint URL
23
+ * @param apiKeyId - Your Kalshi API key ID
24
+ * @param privateKey - Your private key in PEM format
25
+ * @returns Full WebSocket URL with auth parameters
26
+ */
27
+ export declare function generateSignedWsUrl(baseUrl: string, apiKeyId: string, privateKey: string): string;
28
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/websocket/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,MAAmB,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,MAAM,CAUR"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Kalshi WebSocket Authentication
3
+ *
4
+ * Handles signature generation for authenticated WebSocket connections.
5
+ * Uses the same RSA-PSS signing as the REST API.
6
+ */
7
+ import * as crypto from 'crypto';
8
+ /**
9
+ * Generate authentication headers for WebSocket connection
10
+ *
11
+ * @param apiKeyId - Your Kalshi API key ID
12
+ * @param privateKey - Your private key in PEM format
13
+ * @param timestamp - Unix timestamp in milliseconds
14
+ * @returns Headers object for WebSocket connection
15
+ */
16
+ export function generateWsAuthHeaders(apiKeyId, privateKey, timestamp = Date.now()) {
17
+ // The message to sign for WebSocket is typically the timestamp
18
+ const timestampStr = timestamp.toString();
19
+ // Create signature using RSA-PSS with SHA-256
20
+ const sign = crypto.createSign('RSA-SHA256');
21
+ sign.update(timestampStr);
22
+ sign.end();
23
+ // Sign with PSS padding
24
+ const signature = sign.sign({
25
+ key: privateKey,
26
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
27
+ saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
28
+ }, 'base64');
29
+ return {
30
+ 'KALSHI-ACCESS-KEY': apiKeyId,
31
+ 'KALSHI-ACCESS-SIGNATURE': signature,
32
+ 'KALSHI-ACCESS-TIMESTAMP': timestampStr,
33
+ };
34
+ }
35
+ /**
36
+ * Generate a signed WebSocket URL with auth params
37
+ *
38
+ * Some WebSocket implementations don't support custom headers,
39
+ * so we can pass auth as query parameters instead.
40
+ *
41
+ * @param baseUrl - WebSocket endpoint URL
42
+ * @param apiKeyId - Your Kalshi API key ID
43
+ * @param privateKey - Your private key in PEM format
44
+ * @returns Full WebSocket URL with auth parameters
45
+ */
46
+ export function generateSignedWsUrl(baseUrl, apiKeyId, privateKey) {
47
+ const timestamp = Date.now();
48
+ const headers = generateWsAuthHeaders(apiKeyId, privateKey, timestamp);
49
+ const url = new URL(baseUrl);
50
+ url.searchParams.set('api_key', headers['KALSHI-ACCESS-KEY']);
51
+ url.searchParams.set('signature', headers['KALSHI-ACCESS-SIGNATURE']);
52
+ url.searchParams.set('timestamp', headers['KALSHI-ACCESS-TIMESTAMP']);
53
+ return url.toString();
54
+ }
55
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/websocket/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,UAAkB,EAClB,YAAoB,IAAI,CAAC,GAAG,EAAE;IAE9B,+DAA+D;IAC/D,MAAM,YAAY,GAAG,SAAS,CAAC,QAAQ,EAAE,CAAC;IAE1C,8CAA8C;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC1B,IAAI,CAAC,GAAG,EAAE,CAAC;IAEX,wBAAwB;IACxB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1B,GAAG,EAAE,UAAU;QACf,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,qBAAqB;QAC/C,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,sBAAsB;KACpD,EAAE,QAAQ,CAAC,CAAC;IAEb,OAAO;QACL,mBAAmB,EAAE,QAAQ;QAC7B,yBAAyB,EAAE,SAAS;QACpC,yBAAyB,EAAE,YAAY;KACxC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,QAAgB,EAChB,UAAkB;IAElB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,qBAAqB,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IAEvE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC9D,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC;IACtE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAEtE,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/websocket/auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ // Mock crypto module since we can't use real RSA keys in tests easily
3
+ vi.mock('crypto', async () => {
4
+ const actual = await vi.importActual('crypto');
5
+ return {
6
+ ...actual,
7
+ createSign: vi.fn(() => ({
8
+ update: vi.fn().mockReturnThis(),
9
+ end: vi.fn().mockReturnThis(),
10
+ sign: vi.fn(() => 'mocked-signature-base64'),
11
+ })),
12
+ };
13
+ });
14
+ import { generateWsAuthHeaders, generateSignedWsUrl } from './auth.js';
15
+ describe('WebSocket Auth', () => {
16
+ describe('generateWsAuthHeaders', () => {
17
+ it('generates all required headers', () => {
18
+ const headers = generateWsAuthHeaders('test-api-key', 'fake-private-key');
19
+ expect(headers).toHaveProperty('KALSHI-ACCESS-KEY');
20
+ expect(headers).toHaveProperty('KALSHI-ACCESS-SIGNATURE');
21
+ expect(headers).toHaveProperty('KALSHI-ACCESS-TIMESTAMP');
22
+ });
23
+ it('uses the provided API key', () => {
24
+ const headers = generateWsAuthHeaders('my-api-key-123', 'fake-key');
25
+ expect(headers['KALSHI-ACCESS-KEY']).toBe('my-api-key-123');
26
+ });
27
+ it('uses the provided timestamp', () => {
28
+ const timestamp = 1703721600000; // Fixed timestamp
29
+ const headers = generateWsAuthHeaders('test-key', 'fake-key', timestamp);
30
+ expect(headers['KALSHI-ACCESS-TIMESTAMP']).toBe('1703721600000');
31
+ });
32
+ it('returns a signature', () => {
33
+ const headers = generateWsAuthHeaders('test-key', 'fake-key');
34
+ expect(headers['KALSHI-ACCESS-SIGNATURE']).toBeDefined();
35
+ expect(headers['KALSHI-ACCESS-SIGNATURE'].length).toBeGreaterThan(0);
36
+ });
37
+ });
38
+ describe('generateSignedWsUrl', () => {
39
+ it('appends auth parameters to URL', () => {
40
+ const url = generateSignedWsUrl('wss://api.example.com/ws', 'test-api-key', 'fake-key');
41
+ expect(url).toContain('api_key=test-api-key');
42
+ expect(url).toContain('signature=');
43
+ expect(url).toContain('timestamp=');
44
+ });
45
+ it('preserves the base URL', () => {
46
+ const url = generateSignedWsUrl('wss://api.elections.kalshi.com/trade-api/ws/v2', 'test-key', 'fake-key');
47
+ expect(url.startsWith('wss://api.elections.kalshi.com/trade-api/ws/v2')).toBe(true);
48
+ });
49
+ it('properly encodes parameters', () => {
50
+ const url = generateSignedWsUrl('wss://api.example.com/ws', 'test+key', 'fake-key');
51
+ // URL should properly encode the + character
52
+ expect(url).toContain('api_key=test%2Bkey');
53
+ });
54
+ });
55
+ });
56
+ //# sourceMappingURL=auth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/websocket/auth.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Kalshi WebSocket Client
3
+ *
4
+ * Provides real-time market data streaming from Kalshi.
5
+ * Features:
6
+ * - Automatic reconnection with exponential backoff
7
+ * - Subscription management
8
+ * - Event-based message handling
9
+ * - Connection health monitoring
10
+ */
11
+ import { type KalshiWsConfig, type KalshiWsEventHandlers, type SubscriptionChannel, type ConnectionState } from './types.js';
12
+ /**
13
+ * Kalshi WebSocket Client
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const client = new KalshiWsClient({
18
+ * apiKeyId: 'your-api-key',
19
+ * privateKey: 'your-private-key',
20
+ * });
21
+ *
22
+ * client.on('ticker', (data) => {
23
+ * console.log('Price update:', data.market_ticker, data.yes_bid);
24
+ * });
25
+ *
26
+ * await client.connect();
27
+ * client.subscribe(['ticker'], ['KXBTC-25JAN03']);
28
+ * ```
29
+ */
30
+ export declare class KalshiWsClient {
31
+ private config;
32
+ private ws;
33
+ private handlers;
34
+ private state;
35
+ private reconnectAttempts;
36
+ private commandId;
37
+ private pingTimer;
38
+ private pongTimer;
39
+ private subscriptions;
40
+ constructor(config: KalshiWsConfig);
41
+ /**
42
+ * Get current connection state
43
+ */
44
+ get connectionState(): ConnectionState;
45
+ /**
46
+ * Check if connected
47
+ */
48
+ get isConnected(): boolean;
49
+ /**
50
+ * Register event handlers
51
+ */
52
+ on<K extends keyof KalshiWsEventHandlers>(event: K, handler: KalshiWsEventHandlers[K]): void;
53
+ /**
54
+ * Connect to WebSocket server
55
+ */
56
+ connect(): Promise<void>;
57
+ /**
58
+ * Disconnect from WebSocket server
59
+ */
60
+ disconnect(): void;
61
+ /**
62
+ * Subscribe to channels for specific market tickers
63
+ *
64
+ * @param channels - Channels to subscribe to
65
+ * @param marketTickers - Market tickers (not needed for 'fill' channel)
66
+ */
67
+ subscribe(channels: SubscriptionChannel[], marketTickers?: string[]): void;
68
+ /**
69
+ * Unsubscribe from channels
70
+ */
71
+ unsubscribe(channels: SubscriptionChannel[], marketTickers?: string[]): void;
72
+ /**
73
+ * Add markets to existing subscription
74
+ */
75
+ addMarkets(channels: SubscriptionChannel[], marketTickers: string[]): void;
76
+ /**
77
+ * Remove markets from existing subscription
78
+ */
79
+ removeMarkets(channels: SubscriptionChannel[], marketTickers: string[]): void;
80
+ /**
81
+ * Get currently subscribed tickers for a channel
82
+ */
83
+ getSubscribedTickers(channel: SubscriptionChannel): string[];
84
+ private send;
85
+ private handleMessage;
86
+ private handleClose;
87
+ private scheduleReconnect;
88
+ private resubscribeAll;
89
+ private updateLocalSubscriptions;
90
+ private startPingPong;
91
+ private stopPingPong;
92
+ private setPongTimeout;
93
+ private clearPongTimeout;
94
+ }
95
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/websocket/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAG1B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAGrB,MAAM,YAAY,CAAC;AAYpB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,aAAa,CAKnB;gBAEU,MAAM,EAAE,cAAc;IAclC;;OAEG;IACH,IAAI,eAAe,IAAI,eAAe,CAErC;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,qBAAqB,EACtC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAChC,IAAI;IAIP;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiD9B;;OAEG;IACH,UAAU,IAAI,IAAI;IAUlB;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI;IAoB1E;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI;IAmB5E;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAoB1E;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAoB7E;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,EAAE;IAS5D,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,aAAa;IAyCrB,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,cAAc;IAsBtB,OAAO,CAAC,wBAAwB;IAoBhC,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,gBAAgB;CAMzB"}