@whitewall/blip-sdk 0.0.176 → 0.0.177
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/sender/http/httpsender.js +20 -10
- package/dist/cjs/sender/http/httpsender.js.map +1 -1
- package/dist/cjs/sender/security.js +6 -0
- package/dist/cjs/sender/security.js.map +1 -1
- package/dist/cjs/sender/sender.js +4 -2
- package/dist/cjs/sender/sender.js.map +1 -1
- package/dist/cjs/sender/sessionnegotiator.js +11 -4
- package/dist/cjs/sender/sessionnegotiator.js.map +1 -1
- package/dist/cjs/sender/tcp/tcpsender.js +60 -15
- package/dist/cjs/sender/tcp/tcpsender.js.map +1 -1
- package/dist/cjs/sender/transporttls.js +16 -0
- package/dist/cjs/sender/transporttls.js.map +1 -0
- package/dist/cjs/sender/websocket/websocketsender.js +2 -0
- package/dist/cjs/sender/websocket/websocketsender.js.map +1 -1
- package/dist/esm/sender/http/httpsender.js +20 -10
- package/dist/esm/sender/http/httpsender.js.map +1 -1
- package/dist/esm/sender/security.js +5 -1
- package/dist/esm/sender/security.js.map +1 -1
- package/dist/esm/sender/sender.js +4 -2
- package/dist/esm/sender/sender.js.map +1 -1
- package/dist/esm/sender/sessionnegotiator.js +11 -4
- package/dist/esm/sender/sessionnegotiator.js.map +1 -1
- package/dist/esm/sender/tcp/tcpsender.js +60 -15
- package/dist/esm/sender/tcp/tcpsender.js.map +1 -1
- package/dist/esm/sender/transporttls.js +13 -0
- package/dist/esm/sender/transporttls.js.map +1 -0
- package/dist/esm/sender/websocket/websocketsender.js +2 -0
- package/dist/esm/sender/websocket/websocketsender.js.map +1 -1
- package/dist/types/sender/http/httpsender.d.ts +2 -1
- package/dist/types/sender/http/httpsender.d.ts.map +1 -1
- package/dist/types/sender/security.d.ts +12 -1
- package/dist/types/sender/security.d.ts.map +1 -1
- package/dist/types/sender/sender.d.ts +2 -2
- package/dist/types/sender/sender.d.ts.map +1 -1
- package/dist/types/sender/sessionnegotiator.d.ts +1 -0
- package/dist/types/sender/sessionnegotiator.d.ts.map +1 -1
- package/dist/types/sender/tcp/tcpsender.d.ts.map +1 -1
- package/dist/types/sender/transporttls.d.ts +13 -0
- package/dist/types/sender/transporttls.d.ts.map +1 -0
- package/dist/types/sender/websocket/websocketsender.d.ts +2 -1
- package/dist/types/sender/websocket/websocketsender.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/sender/http/httpsender.ts +22 -11
- package/src/sender/security.ts +21 -0
- package/src/sender/sender.ts +9 -7
- package/src/sender/sessionnegotiator.ts +19 -5
- package/src/sender/tcp/tcpsender.ts +72 -19
- package/src/sender/transporttls.ts +16 -0
- package/src/sender/websocket/websocketsender.ts +6 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Socket } from 'node:net'
|
|
2
|
+
import type { ConnectionOptions } from 'node:tls'
|
|
2
3
|
import type {
|
|
3
4
|
Command,
|
|
4
5
|
CommandMethods,
|
|
@@ -9,11 +10,13 @@ import type {
|
|
|
9
10
|
Notification,
|
|
10
11
|
UnknownCommandResponse,
|
|
11
12
|
} from '../../types/index.ts'
|
|
13
|
+
import { logger } from '../../utils/logger.ts'
|
|
12
14
|
import { BlipError } from '../bliperror.ts'
|
|
13
15
|
import { RetryableError } from '../retryableerror.ts'
|
|
14
16
|
import { ConnectionSender, type ConnectionSenderConstructor, OpenConnectionSender } from '../sender.ts'
|
|
15
17
|
import { SessionNegotiator } from '../sessionnegotiator.ts'
|
|
16
18
|
import { EnvelopeThrottler } from '../throttler.ts'
|
|
19
|
+
import { buildTlsConnectOptions } from '../transporttls.ts'
|
|
17
20
|
import { tryParseJSON } from './json-parser.ts'
|
|
18
21
|
|
|
19
22
|
/**
|
|
@@ -30,16 +33,26 @@ export class TCPSender extends OpenConnectionSender {
|
|
|
30
33
|
super(options)
|
|
31
34
|
|
|
32
35
|
const prefix = options.tenantId ? `${options.tenantId}.` : ''
|
|
36
|
+
const host = `${prefix}tcp.${this.domain}`
|
|
37
|
+
const auth = options.authentication
|
|
38
|
+
const tlsOptions: ConnectionOptions | null =
|
|
39
|
+
auth.scheme === 'transport' ? { ...buildTlsConnectOptions(auth), servername: host } : null
|
|
40
|
+
|
|
33
41
|
this.connectionHandle = new TCPHandle<Envelope>(
|
|
34
|
-
|
|
42
|
+
host,
|
|
35
43
|
443,
|
|
36
|
-
(
|
|
44
|
+
() => {
|
|
37
45
|
this.sessionNegotiator = new SessionNegotiator(this, (session) => {
|
|
38
|
-
|
|
46
|
+
this.connectionHandle
|
|
47
|
+
.get()
|
|
48
|
+
.then((s) => s.write(JSON.stringify(session)))
|
|
49
|
+
.catch((err) => logger.warn('TCPSender', 'Failed to write session frame', err))
|
|
39
50
|
})
|
|
51
|
+
const upgradeToTls = tlsOptions ? () => this.connectionHandle.upgradeToTls(tlsOptions) : undefined
|
|
40
52
|
return this.sessionNegotiator.negotiate({
|
|
41
53
|
node: options.node,
|
|
42
|
-
authentication:
|
|
54
|
+
authentication: auth,
|
|
55
|
+
upgradeToTls,
|
|
43
56
|
})
|
|
44
57
|
},
|
|
45
58
|
() => this.envelopeResolver.rejectPendingEnvelopes('Connection was closed'),
|
|
@@ -124,15 +137,18 @@ class TCPHandle<T> {
|
|
|
124
137
|
private currentSocketPromise: Promise<Socket> | null = null
|
|
125
138
|
private closing = false
|
|
126
139
|
private connectionAttempts = 0
|
|
140
|
+
private buffer = Buffer.alloc(0)
|
|
141
|
+
private readonly onMessage: (message: T) => void
|
|
127
142
|
|
|
128
143
|
constructor(
|
|
129
144
|
host: string,
|
|
130
145
|
port: number,
|
|
131
|
-
onConnected: (
|
|
146
|
+
onConnected: () => Promise<void>,
|
|
132
147
|
onClose: () => void,
|
|
133
148
|
onMessage: (message: T) => void,
|
|
134
149
|
) {
|
|
135
|
-
this.
|
|
150
|
+
this.onMessage = onMessage
|
|
151
|
+
this.currentSocketPromise = this.connect(host, port, onConnected, onClose)
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
public get() {
|
|
@@ -151,17 +167,55 @@ class TCPHandle<T> {
|
|
|
151
167
|
}
|
|
152
168
|
}
|
|
153
169
|
|
|
170
|
+
public async upgradeToTls(options: ConnectionOptions): Promise<void> {
|
|
171
|
+
if (!this.currentSocketPromise) {
|
|
172
|
+
throw new Error('Cannot upgrade: no active socket.')
|
|
173
|
+
}
|
|
174
|
+
const plain = await this.currentSocketPromise
|
|
175
|
+
plain.removeAllListeners('data')
|
|
176
|
+
this.buffer = Buffer.alloc(0)
|
|
177
|
+
|
|
178
|
+
const { connect: tlsConnect } = await import('node:tls')
|
|
179
|
+
const secured = tlsConnect({ ...options, socket: plain })
|
|
180
|
+
|
|
181
|
+
await new Promise<void>((resolve, reject) => {
|
|
182
|
+
const onError = (err: Error) => {
|
|
183
|
+
secured.off('secureConnect', onConnect)
|
|
184
|
+
reject(err)
|
|
185
|
+
}
|
|
186
|
+
const onConnect = () => {
|
|
187
|
+
secured.off('error', onError)
|
|
188
|
+
resolve()
|
|
189
|
+
}
|
|
190
|
+
secured.once('secureConnect', onConnect)
|
|
191
|
+
secured.once('error', onError)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
this.attachDataListener(secured)
|
|
195
|
+
this.currentSocketPromise = Promise.resolve(secured)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private attachDataListener(socket: NodeJS.ReadableStream) {
|
|
199
|
+
socket.on('data', (chunk: Buffer<ArrayBuffer>) => {
|
|
200
|
+
const result = tryParseJSON<T>(this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]))
|
|
201
|
+
this.buffer = result.remainingBuffer
|
|
202
|
+
|
|
203
|
+
for (const parsed of result.parsedObjects) {
|
|
204
|
+
this.onMessage(parsed)
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
154
209
|
private async connect(
|
|
155
210
|
host: string,
|
|
156
211
|
port: number,
|
|
157
|
-
onConnected: (
|
|
212
|
+
onConnected: () => Promise<void>,
|
|
158
213
|
onClose: () => void,
|
|
159
|
-
onMessage: (message: T) => void,
|
|
160
214
|
): Promise<Socket> {
|
|
161
215
|
const { connect } = await import('node:net')
|
|
162
216
|
|
|
163
217
|
const socket = connect({ host, port }).setKeepAlive(true)
|
|
164
|
-
|
|
218
|
+
this.buffer = Buffer.alloc(0)
|
|
165
219
|
|
|
166
220
|
await new Promise<void>((resolve) => {
|
|
167
221
|
socket.once('connect', () => {
|
|
@@ -175,29 +229,28 @@ class TCPHandle<T> {
|
|
|
175
229
|
})
|
|
176
230
|
|
|
177
231
|
socket.once('close', () => {
|
|
232
|
+
socket.removeAllListeners('data')
|
|
178
233
|
if (!this.closing) {
|
|
179
234
|
onClose()
|
|
180
235
|
|
|
181
236
|
this.connectionAttempts++
|
|
182
237
|
if (this.connectionAttempts < 3) {
|
|
183
|
-
this.currentSocketPromise = this.connect(host, port, onConnected, onClose
|
|
238
|
+
this.currentSocketPromise = this.connect(host, port, onConnected, onClose)
|
|
184
239
|
} else {
|
|
185
240
|
throw new Error('Failed to connect/reconnect to TCP socket')
|
|
186
241
|
}
|
|
187
242
|
}
|
|
188
243
|
})
|
|
189
244
|
|
|
190
|
-
|
|
191
|
-
const result = tryParseJSON<T>(buffer.length === 0 ? chunk : Buffer.concat([buffer, chunk]))
|
|
192
|
-
buffer = result.remainingBuffer
|
|
193
|
-
|
|
194
|
-
for (const parsed of result.parsedObjects) {
|
|
195
|
-
onMessage(parsed)
|
|
196
|
-
}
|
|
197
|
-
})
|
|
245
|
+
this.attachDataListener(socket)
|
|
198
246
|
})
|
|
199
247
|
|
|
200
|
-
|
|
248
|
+
// sendSession callbacks read currentSocketPromise via .get(); resolve it
|
|
249
|
+
// before onConnected runs so negotiate() can write the 'new' session frame
|
|
250
|
+
// (otherwise we deadlock waiting on the outer connect() promise).
|
|
251
|
+
this.currentSocketPromise = Promise.resolve(socket)
|
|
252
|
+
|
|
253
|
+
await onConnected()
|
|
201
254
|
this.connectionAttempts = 0
|
|
202
255
|
|
|
203
256
|
return socket
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TransportAuthentication } from './security.ts'
|
|
2
|
+
|
|
3
|
+
export function buildTlsConnectOptions(auth: TransportAuthentication) {
|
|
4
|
+
const toBuf = (v: Uint8Array | string) => (typeof v === 'string' ? v : Buffer.from(v))
|
|
5
|
+
const toBufOrArray = (v: TransportAuthentication['ca']) =>
|
|
6
|
+
Array.isArray(v) ? v.map(toBuf) : v !== undefined ? toBuf(v) : undefined
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
pfx: auth.pfx ? [{ buf: Buffer.from(auth.pfx), passphrase: auth.passphrase }] : undefined,
|
|
10
|
+
cert: auth.cert !== undefined ? toBuf(auth.cert) : undefined,
|
|
11
|
+
key: auth.key !== undefined ? toBuf(auth.key) : undefined,
|
|
12
|
+
ca: toBufOrArray(auth.ca),
|
|
13
|
+
passphrase: auth.passphrase,
|
|
14
|
+
rejectUnauthorized: auth.rejectUnauthorized ?? true,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { logger } from '../../utils/logger.ts'
|
|
12
12
|
import { BlipError } from '../bliperror.ts'
|
|
13
13
|
import { RetryableError } from '../retryableerror.ts'
|
|
14
|
+
import { assertNonTransportAuth, type NonTransportAuthentication } from '../security.ts'
|
|
14
15
|
import { ConnectionSender, type ConnectionSenderConstructor, OpenConnectionSender } from '../sender.ts'
|
|
15
16
|
import { SessionNegotiator } from '../sessionnegotiator.ts'
|
|
16
17
|
import { EnvelopeThrottler } from '../throttler.ts'
|
|
@@ -19,10 +20,14 @@ export class WebSocketSender extends OpenConnectionSender {
|
|
|
19
20
|
private readonly throttler = new EnvelopeThrottler()
|
|
20
21
|
private readonly connectionHandle: WebSocketHandle<Envelope>
|
|
21
22
|
|
|
22
|
-
constructor(
|
|
23
|
+
constructor(
|
|
24
|
+
options: ConstructorParameters<ConnectionSenderConstructor<WebSocketSender, NonTransportAuthentication>>[0],
|
|
25
|
+
) {
|
|
23
26
|
super(options)
|
|
27
|
+
assertNonTransportAuth(options.authentication, 'WebSocketSender')
|
|
24
28
|
|
|
25
29
|
const prefix = options.tenantId ? `${options.tenantId}.` : ''
|
|
30
|
+
|
|
26
31
|
this.connectionHandle = new WebSocketHandle<Envelope>(
|
|
27
32
|
`wss://${prefix}ws.${this.domain}`,
|
|
28
33
|
(webSocket) => {
|