Skip to content

Commit

Permalink
0.1.0-0.3.0 - Feature: TCP Socket (#24)
Browse files Browse the repository at this point in the history
* Add tcpSocket option for direct connections

* Dynamically import socket wrapper

* Increment version

* Update README
  • Loading branch information
lnbc1QWFyb24 authored Mar 21, 2023
1 parent e910827 commit 22b84ee
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 11 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Talk to Lightning nodes from the Browser and NodeJS apps.

## Features

- Connect to a lightning node via a WebSocket connection.
- Connect to a lightning node via a WebSocket or TCP Socket connection.
- Works in the Browser and Node without any polyfilling.
- Initialise with a session secret to have a persistent node public key for the browser.
- Control a Core Lightning node via [Commando](https://lightning.readthedocs.io/lightning-commando.7.html) RPC calls.
Expand Down Expand Up @@ -84,6 +84,8 @@ type LnWebSocketOptions = {
* When connecting directly to a node and not using a proxy, the protocol to use. Defaults to 'wss://'
*/
wsProtocol?: 'ws:' | 'wss:'
/**In Nodejs or React Native you can connect directly via a TCP socket */
tcpSocket?: TCPSocket
/**
* 32 byte hex encoded private key to be used as the local node secret.
* Use this to ensure a consistent local node identity across connection sessions
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lnmessage",
"version": "0.1.0-0.1.0",
"version": "0.1.0-0.3.0",
"description": "Talk to Lightning nodes from your browser",
"main": "dist/index.js",
"type": "module",
Expand All @@ -10,6 +10,7 @@
],
"author": "Aaron Barnard",
"license": "MIT",
"repository": "github:aaronbarnardsound/lnmessage",
"scripts": {
"build": "tsc"
},
Expand Down
25 changes: 19 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { CommandoMessage } from './messages/CommandoMessage.js'
import { PongMessage } from './messages/PongMessage.js'
import { PingMessage } from './messages/PingMessage.js'
import type { WebSocket as NodeWebSocket } from 'ws'
import type { Socket as TCPSocket } from 'net'
import type SocketWrapper from './socket-wrapper.js'

import {
LnWebSocketOptions,
Expand Down Expand Up @@ -47,7 +49,9 @@ class LnMessage {
*/
public wsUrl: string
/**The WebSocket instance*/
public socket: WebSocket | NodeWebSocket | null
public socket: WebSocket | NodeWebSocket | null | SocketWrapper
/**TCP socket instance*/
public tcpSocket?: TCPSocket
/**
* @deprecated Use connectionStatus$ instead
*/
Expand Down Expand Up @@ -96,7 +100,8 @@ class LnMessage {
privateKey,
ip,
port = 9735,
logger
logger,
tcpSocket
} = options

this._ls = Buffer.from(privateKey || createRandomPrivateKey(), 'hex')
Expand All @@ -115,6 +120,7 @@ class LnMessage {
this.connected$ = new BehaviorSubject<boolean>(false)
this.connecting = false
this.Buffer = Buffer
this.tcpSocket = tcpSocket

this._handshakeState = HANDSHAKE_STATE.INITIATOR_INITIATING
this._decryptedMsgs$ = new Subject()
Expand Down Expand Up @@ -159,8 +165,15 @@ class LnMessage {
this.connectionStatus$.next('connecting')
this._attemptReconnect = attemptReconnect

this.socket = new (typeof globalThis.WebSocket === 'undefined' ? (await import('ws')).default : globalThis.WebSocket)(this.wsUrl)
this.socket.binaryType = 'arraybuffer'
this.socket = this.tcpSocket
? new (await import('./socket-wrapper.js')).default(this.wsUrl, this.tcpSocket)
: typeof globalThis.WebSocket === 'undefined'
? new (await import('ws')).default(this.wsUrl)
: new globalThis.WebSocket(this.wsUrl)

if ((this.socket as WebSocket | NodeWebSocket).binaryType) {
;(this.socket as WebSocket | NodeWebSocket).binaryType = 'arraybuffer'
}

this.socket.onopen = async () => {
this._log('info', 'WebSocket is connected')
Expand Down Expand Up @@ -209,8 +222,8 @@ class LnMessage {
)
}

private queueMessage(event: MessageEvent) {
const { data } = event as { data: ArrayBuffer }
private queueMessage(event: { data: ArrayBuffer }) {
const { data } = event
const message = Buffer.from(data)

const currentData =
Expand Down
6 changes: 3 additions & 3 deletions src/noise-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class NoiseState {
// 4. ck, temp_k1 = HKDF(ck, es)
const tempK1 = hkdf(ss, 64, this.ck)
this.ck = tempK1.subarray(0, 32)
this.tempK1 = tempK1.subarray(32)
this.tempK1 = Buffer.from(tempK1.subarray(32))

// 5. c = encryptWithAD(temp_k1, 0, h, zero)
const c = ccpEncrypt(this.tempK1, Buffer.alloc(12), this.h, Buffer.alloc(0))
Expand Down Expand Up @@ -183,7 +183,7 @@ export class NoiseState {
// 4. ck, temp_k3 = HKDF(ck, ss)
const tempK3 = hkdf(ss, 64, this.ck)
this.ck = tempK3.subarray(0, 32)
this.tempK3 = tempK3.subarray(32)
this.tempK3 = Buffer.from(tempK3.subarray(32))
// 5. t = encryptWithAD(temp_k3, 0, h, zero)
const t = ccpEncrypt(this.tempK3, Buffer.alloc(12), this.h, Buffer.alloc(0))
// 6. sk, rk = hkdf(ck, zero)
Expand Down Expand Up @@ -239,7 +239,7 @@ export class NoiseState {
// 4. ck, temp_k2 = hkdf(ck, ss)
const tempK2 = hkdf(ss, 64, this.ck)
this.ck = tempK2.subarray(0, 32)
this.tempK2 = tempK2.subarray(32)
this.tempK2 = Buffer.from(tempK2.subarray(32))
// 5. c = encryptWithAd(temp_k2, 0, h, zero)
const c = ccpEncrypt(this.tempK2, Buffer.alloc(12), this.h, Buffer.alloc(0))
// 6. h = sha256(h || c)
Expand Down
47 changes: 47 additions & 0 deletions src/socket-wrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Socket } from 'net'
import type { Buffer } from 'buffer'

//**Wraps a TCP socket with the WebSocket API */
class SocketWrapper {
public onopen?: () => void
public onclose?: () => void
public onerror?: (error: { message: string }) => void
public onmessage?: (event: { data: ArrayBuffer }) => void
public send: (message: Buffer) => void
public close: () => void

constructor(connection: string, socket: Socket) {
socket.on('connect', () => {
this.onopen && this.onopen()
})

socket.on('close', () => {
this.onclose && this.onclose()
})

socket.on('error', (error) => {
this.onerror && this.onerror(error)
})

socket.on('data', (data) => {
this.onmessage && this.onmessage({ data })
})

this.send = (message: Buffer) => {
socket.write(message)
}

this.close = () => {
socket.removeAllListeners()
socket.destroy()
}

const url = new URL(connection)
const { host } = url
const [nodeIP, port] = host.split(':')

socket.connect(parseInt(port), nodeIP)
}
}

export default SocketWrapper
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Buffer } from 'buffer'
import type { Socket as TCPSocket } from 'net'

export type LnWebSocketOptions = {
/**
Expand All @@ -25,6 +26,8 @@ export type LnWebSocketOptions = {
* When connecting directly to a node, the protocol to use. Defaults to 'wss://'
*/
wsProtocol?: 'ws:' | 'wss:'
/**In nodejs or react native you can connect directly via a TCP socket */
tcpSocket?: TCPSocket
/**
* 32 byte hex encoded private key to be used as the local node secret.
* Use this to ensure a consistent local node identity across connection sessions
Expand Down

0 comments on commit 22b84ee

Please sign in to comment.