@whitewall/blip-sdk 0.0.180 → 0.0.182
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/cjs/namespaces/billing.js +20 -4
- package/dist/cjs/namespaces/billing.js.map +1 -1
- package/dist/cjs/sender/enveloperesolver.js +2 -2
- package/dist/cjs/sender/enveloperesolver.js.map +1 -1
- package/dist/cjs/sender/sender.js +6 -0
- package/dist/cjs/sender/sender.js.map +1 -1
- package/dist/cjs/sender/sessionnegotiator.js +15 -7
- package/dist/cjs/sender/sessionnegotiator.js.map +1 -1
- package/dist/cjs/sender/tcp/tcpsender.js +47 -31
- package/dist/cjs/sender/tcp/tcpsender.js.map +1 -1
- package/dist/cjs/sender/websocket/websocketsender.js +40 -12
- package/dist/cjs/sender/websocket/websocketsender.js.map +1 -1
- package/dist/esm/namespaces/billing.js +20 -4
- package/dist/esm/namespaces/billing.js.map +1 -1
- package/dist/esm/sender/enveloperesolver.js +2 -2
- package/dist/esm/sender/enveloperesolver.js.map +1 -1
- package/dist/esm/sender/sender.js +6 -0
- package/dist/esm/sender/sender.js.map +1 -1
- package/dist/esm/sender/sessionnegotiator.js +15 -7
- package/dist/esm/sender/sessionnegotiator.js.map +1 -1
- package/dist/esm/sender/tcp/tcpsender.js +47 -31
- package/dist/esm/sender/tcp/tcpsender.js.map +1 -1
- package/dist/esm/sender/websocket/websocketsender.js +40 -12
- package/dist/esm/sender/websocket/websocketsender.js.map +1 -1
- package/dist/types/namespaces/billing.d.ts +8 -1
- package/dist/types/namespaces/billing.d.ts.map +1 -1
- package/dist/types/sender/enveloperesolver.d.ts +1 -1
- package/dist/types/sender/enveloperesolver.d.ts.map +1 -1
- package/dist/types/sender/sender.d.ts +1 -0
- package/dist/types/sender/sender.d.ts.map +1 -1
- package/dist/types/sender/sessionnegotiator.d.ts +2 -0
- package/dist/types/sender/sessionnegotiator.d.ts.map +1 -1
- package/dist/types/sender/tcp/tcpsender.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/namespaces/billing.ts +21 -5
- package/src/sender/enveloperesolver.ts +2 -2
- package/src/sender/sender.ts +7 -0
- package/src/sender/sessionnegotiator.ts +16 -7
- package/src/sender/tcp/tcpsender.ts +51 -33
- package/src/sender/websocket/websocketsender.ts +40 -14
|
@@ -10,7 +10,6 @@ import type {
|
|
|
10
10
|
Notification,
|
|
11
11
|
UnknownCommandResponse,
|
|
12
12
|
} from '../../types/index.ts'
|
|
13
|
-
import { logger } from '../../utils/logger.ts'
|
|
14
13
|
import { BlipError } from '../bliperror.ts'
|
|
15
14
|
import { RetryableError } from '../retryableerror.ts'
|
|
16
15
|
import { ConnectionSender, type ConnectionSenderConstructor, OpenConnectionSender } from '../sender.ts'
|
|
@@ -41,21 +40,20 @@ export class TCPSender extends OpenConnectionSender {
|
|
|
41
40
|
this.connectionHandle = new TCPHandle<Envelope>(
|
|
42
41
|
host,
|
|
43
42
|
443,
|
|
44
|
-
() => {
|
|
43
|
+
(socketRef) => {
|
|
45
44
|
this.sessionNegotiator = new SessionNegotiator(this, (session) => {
|
|
46
|
-
|
|
47
|
-
.get()
|
|
48
|
-
.then((s) => s.write(JSON.stringify(session)))
|
|
49
|
-
.catch((err) => logger.warn('TCPSender', 'Failed to write session frame', err))
|
|
45
|
+
socketRef.current.write(JSON.stringify(session))
|
|
50
46
|
})
|
|
51
|
-
const upgradeToTls = tlsOptions
|
|
47
|
+
const upgradeToTls = tlsOptions
|
|
48
|
+
? () => this.connectionHandle.upgradeToTls(tlsOptions, socketRef)
|
|
49
|
+
: undefined
|
|
52
50
|
return this.sessionNegotiator.negotiate({
|
|
53
51
|
node: options.node,
|
|
54
52
|
authentication: auth,
|
|
55
53
|
upgradeToTls,
|
|
56
54
|
})
|
|
57
55
|
},
|
|
58
|
-
() => this.
|
|
56
|
+
() => this.rejectPending('Connection was closed'),
|
|
59
57
|
(envelope: Envelope) => {
|
|
60
58
|
if (this.sessionNegotiator?.negotiating) {
|
|
61
59
|
return this.sessionNegotiator.handleEnvelope(envelope)
|
|
@@ -143,7 +141,7 @@ class TCPHandle<T> {
|
|
|
143
141
|
constructor(
|
|
144
142
|
host: string,
|
|
145
143
|
port: number,
|
|
146
|
-
onConnected: () => Promise<void>,
|
|
144
|
+
onConnected: (socketRef: { current: Socket }) => Promise<void>,
|
|
147
145
|
onClose: () => void,
|
|
148
146
|
onMessage: (message: T) => void,
|
|
149
147
|
) {
|
|
@@ -167,17 +165,19 @@ class TCPHandle<T> {
|
|
|
167
165
|
}
|
|
168
166
|
}
|
|
169
167
|
|
|
170
|
-
public async upgradeToTls(options: ConnectionOptions): Promise<void> {
|
|
171
|
-
|
|
172
|
-
throw new Error('Cannot upgrade: no active socket.')
|
|
173
|
-
}
|
|
174
|
-
const plain = await this.currentSocketPromise
|
|
168
|
+
public async upgradeToTls(options: ConnectionOptions, socketRef: { current: Socket }): Promise<void> {
|
|
169
|
+
const plain = socketRef.current
|
|
175
170
|
plain.removeAllListeners('data')
|
|
176
171
|
this.buffer = Buffer.alloc(0)
|
|
177
172
|
|
|
178
173
|
const { connect: tlsConnect } = await import('node:tls')
|
|
179
174
|
const secured = tlsConnect({ ...options, socket: plain })
|
|
180
175
|
|
|
176
|
+
// Permanent no-op handler attached up front, so errors that arrive in
|
|
177
|
+
// the microtask gap after secureConnect resolves don't become
|
|
178
|
+
// uncaughtException. The underlying TCP socket's 'close' drives reconnect.
|
|
179
|
+
secured.on('error', () => undefined)
|
|
180
|
+
|
|
181
181
|
await new Promise<void>((resolve, reject) => {
|
|
182
182
|
const onError = (err: Error) => {
|
|
183
183
|
secured.off('secureConnect', onConnect)
|
|
@@ -192,7 +192,7 @@ class TCPHandle<T> {
|
|
|
192
192
|
})
|
|
193
193
|
|
|
194
194
|
this.attachDataListener(secured)
|
|
195
|
-
|
|
195
|
+
socketRef.current = secured
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
private attachDataListener(socket: NodeJS.ReadableStream) {
|
|
@@ -209,7 +209,7 @@ class TCPHandle<T> {
|
|
|
209
209
|
private async connect(
|
|
210
210
|
host: string,
|
|
211
211
|
port: number,
|
|
212
|
-
onConnected: () => Promise<void>,
|
|
212
|
+
onConnected: (socketRef: { current: Socket }) => Promise<void>,
|
|
213
213
|
onClose: () => void,
|
|
214
214
|
): Promise<Socket> {
|
|
215
215
|
const { connect } = await import('node:net')
|
|
@@ -217,42 +217,60 @@ class TCPHandle<T> {
|
|
|
217
217
|
const socket = connect({ host, port }).setKeepAlive(true)
|
|
218
218
|
this.buffer = Buffer.alloc(0)
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
// Mutable holder so upgradeToTls can swap the socket mid-handshake;
|
|
221
|
+
// the negotiator's sendSession reads .current at write time, so frames
|
|
222
|
+
// always go to the active socket even after the TLS upgrade.
|
|
223
|
+
const socketRef = { current: socket }
|
|
224
|
+
|
|
225
|
+
await new Promise<void>((resolve, reject) => {
|
|
226
|
+
let connected = false
|
|
227
|
+
|
|
221
228
|
socket.once('connect', () => {
|
|
229
|
+
connected = true
|
|
222
230
|
resolve()
|
|
223
231
|
})
|
|
224
232
|
|
|
225
233
|
socket.once('error', (err) => {
|
|
226
|
-
if (!
|
|
227
|
-
|
|
234
|
+
if (!connected) {
|
|
235
|
+
reject(err)
|
|
228
236
|
}
|
|
237
|
+
// Post-connect errors are followed by 'close', which drives reconnect;
|
|
238
|
+
// this listener exists only to prevent uncaughtException.
|
|
229
239
|
})
|
|
230
240
|
|
|
231
241
|
socket.once('close', () => {
|
|
232
|
-
socket
|
|
242
|
+
// socketRef.current is the TLS-upgraded socket if upgrade ran; the
|
|
243
|
+
// plain one otherwise. Both cases hold the only live data listener.
|
|
244
|
+
socketRef.current.removeAllListeners('data')
|
|
245
|
+
|
|
233
246
|
if (!this.closing) {
|
|
234
|
-
onClose()
|
|
247
|
+
if (connected) onClose()
|
|
235
248
|
|
|
236
249
|
this.connectionAttempts++
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
250
|
+
// Swallow the rejection of the now-orphaned previous promise; any
|
|
251
|
+
// in-flight get() awaiter still sees it via their captured reference.
|
|
252
|
+
const previous = this.currentSocketPromise
|
|
253
|
+
this.currentSocketPromise =
|
|
254
|
+
this.connectionAttempts < 3 ? this.connect(host, port, onConnected, onClose) : null
|
|
255
|
+
previous?.catch(() => undefined)
|
|
242
256
|
}
|
|
257
|
+
|
|
258
|
+
if (!connected) reject(new Error('Socket closed before connect'))
|
|
243
259
|
})
|
|
244
260
|
|
|
245
261
|
this.attachDataListener(socket)
|
|
246
262
|
})
|
|
247
263
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
264
|
+
try {
|
|
265
|
+
await onConnected(socketRef)
|
|
266
|
+
} catch (err) {
|
|
267
|
+
// Handshake failures (timeout, bad response) leave the socket open
|
|
268
|
+
// otherwise; destroy it so the 'close' handler drives reconnect.
|
|
269
|
+
socketRef.current.destroy()
|
|
270
|
+
throw err
|
|
271
|
+
}
|
|
254
272
|
this.connectionAttempts = 0
|
|
255
273
|
|
|
256
|
-
return
|
|
274
|
+
return socketRef.current
|
|
257
275
|
}
|
|
258
276
|
}
|
|
@@ -39,7 +39,7 @@ export class WebSocketSender extends OpenConnectionSender {
|
|
|
39
39
|
authentication: options.authentication,
|
|
40
40
|
})
|
|
41
41
|
},
|
|
42
|
-
() => this.
|
|
42
|
+
() => this.rejectPending('Connection was closed'),
|
|
43
43
|
(envelope: Envelope) => {
|
|
44
44
|
if (this.sessionNegotiator?.negotiating) {
|
|
45
45
|
return this.sessionNegotiator.handleEnvelope(envelope)
|
|
@@ -139,7 +139,10 @@ class WebSocketHandle<T> {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
public get() {
|
|
142
|
-
|
|
142
|
+
if (!this.currentWebSocketPromise) {
|
|
143
|
+
throw new Error('WebSocket connection is not available.')
|
|
144
|
+
}
|
|
145
|
+
return this.currentWebSocketPromise
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
public async close() {
|
|
@@ -159,31 +162,54 @@ class WebSocketHandle<T> {
|
|
|
159
162
|
const connection = new WebSocket(url, 'lime')
|
|
160
163
|
|
|
161
164
|
await new Promise<void>((resolve, reject) => {
|
|
165
|
+
let connected = false
|
|
166
|
+
|
|
162
167
|
connection.onopen = () => {
|
|
168
|
+
connected = true
|
|
163
169
|
resolve()
|
|
164
170
|
}
|
|
171
|
+
connection.onerror = (err) => {
|
|
172
|
+
if (!connected) {
|
|
173
|
+
reject('message' in err ? new Error(`WebSocket error: ${err.message}`, { cause: err }) : err)
|
|
174
|
+
}
|
|
175
|
+
// Post-open errors are followed by 'close', which drives reconnect.
|
|
176
|
+
}
|
|
165
177
|
connection.onclose = () => {
|
|
166
178
|
if (!this.closing) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (this.connectionAttempts < 3) {
|
|
170
|
-
this.currentWebSocketPromise = this.connect(url, onConnected, onClose, onMessage)
|
|
171
|
-
} else {
|
|
172
|
-
reject(new Error('Failed to connect to WebSocket'))
|
|
173
|
-
}
|
|
179
|
+
if (connected) onClose()
|
|
174
180
|
|
|
175
|
-
|
|
181
|
+
this.connectionAttempts++
|
|
182
|
+
// Swallow the rejection of the now-orphaned previous promise; any
|
|
183
|
+
// in-flight get() awaiter still sees it via their captured reference.
|
|
184
|
+
const previous = this.currentWebSocketPromise
|
|
185
|
+
this.currentWebSocketPromise =
|
|
186
|
+
this.connectionAttempts < 3 ? this.connect(url, onConnected, onClose, onMessage) : null
|
|
187
|
+
previous?.catch(() => undefined)
|
|
176
188
|
}
|
|
189
|
+
|
|
190
|
+
if (!connected) reject(new Error('WebSocket closed before open'))
|
|
177
191
|
}
|
|
178
192
|
connection.onmessage = (event) => {
|
|
179
193
|
onMessage(JSON.parse(event.data))
|
|
180
194
|
}
|
|
181
|
-
connection.onerror = (err) => {
|
|
182
|
-
reject('message' in err ? new Error(`WebSocket error: ${err.message}`, { cause: err }) : err)
|
|
183
|
-
}
|
|
184
195
|
})
|
|
185
196
|
|
|
186
|
-
|
|
197
|
+
try {
|
|
198
|
+
await onConnected(connection)
|
|
199
|
+
} catch (err) {
|
|
200
|
+
// Handshake failures leave the socket open otherwise; tear it down so
|
|
201
|
+
// the 'onclose' handler drives reconnect. Prefer ws.terminate() (hard
|
|
202
|
+
// kill, Node only) to avoid waiting on the graceful close handshake
|
|
203
|
+
// with a peer that just failed to negotiate; fall back to close() in
|
|
204
|
+
// browsers where terminate() doesn't exist.
|
|
205
|
+
const terminable = connection as WebSocket & { terminate?: () => void }
|
|
206
|
+
if (terminable.terminate) {
|
|
207
|
+
terminable.terminate()
|
|
208
|
+
} else {
|
|
209
|
+
connection.close()
|
|
210
|
+
}
|
|
211
|
+
throw err
|
|
212
|
+
}
|
|
187
213
|
this.connectionAttempts = 0
|
|
188
214
|
|
|
189
215
|
return connection
|