@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.
- package/dist/cache.d.ts +71 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +96 -0
- package/dist/cache.js.map +1 -0
- package/dist/cache.test.d.ts +5 -0
- package/dist/cache.test.d.ts.map +1 -0
- package/dist/cache.test.js +108 -0
- package/dist/cache.test.js.map +1 -0
- package/dist/config.d.ts +61 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +56 -5
- package/dist/config.js.map +1 -1
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +94 -0
- package/dist/config.test.js.map +1 -0
- package/dist/format.d.ts +110 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +151 -2
- package/dist/format.js.map +1 -1
- package/dist/format.test.js +107 -2
- package/dist/format.test.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/rate-limiter.d.ts +80 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +180 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/rate-limiter.test.d.ts +5 -0
- package/dist/rate-limiter.test.d.ts.map +1 -0
- package/dist/rate-limiter.test.js +175 -0
- package/dist/rate-limiter.test.js.map +1 -0
- package/dist/websocket/auth.d.ts +28 -0
- package/dist/websocket/auth.d.ts.map +1 -0
- package/dist/websocket/auth.js +55 -0
- package/dist/websocket/auth.js.map +1 -0
- package/dist/websocket/auth.test.d.ts +2 -0
- package/dist/websocket/auth.test.d.ts.map +1 -0
- package/dist/websocket/auth.test.js +56 -0
- package/dist/websocket/auth.test.js.map +1 -0
- package/dist/websocket/client.d.ts +95 -0
- package/dist/websocket/client.d.ts.map +1 -0
- package/dist/websocket/client.js +352 -0
- package/dist/websocket/client.js.map +1 -0
- package/dist/websocket/index.d.ts +9 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/dist/websocket/index.js +9 -0
- package/dist/websocket/index.js.map +1 -0
- package/dist/websocket/types.d.ts +160 -0
- package/dist/websocket/types.d.ts.map +1 -0
- package/dist/websocket/types.js +12 -0
- package/dist/websocket/types.js.map +1 -0
- package/dist/websocket/types.test.d.ts +2 -0
- package/dist/websocket/types.test.d.ts.map +1 -0
- package/dist/websocket/types.test.js +17 -0
- package/dist/websocket/types.test.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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"}
|