Skip to content

Commit 975c0ca

Browse files
Separating tls and websocket implementations
1 parent b4dec0c commit 975c0ca

File tree

9 files changed

+724
-27
lines changed

9 files changed

+724
-27
lines changed

package-lock.json

Lines changed: 84 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
"homepage": "https://github.com/sqlitecloud/sqlitecloud-js#readme",
4444
"dependencies": {
4545
"eventemitter3": "^5.0.1",
46-
"lz4js": "^0.2.0"
46+
"lz4js": "^0.2.0",
47+
"socket.io-client": "^4.7.4"
4748
},
4849
"devDependencies": {
4950
"@types/jest": "^29.5.11",

src/connection-ws.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* connection-ws.ts - handles low level communication with sqlitecloud server via socket.io websocket
3+
*/
4+
5+
import { SQLiteCloudConfig, SQLCloudRowsetMetadata, SQLiteCloudError, SQLiteCloudDataTypes, ErrorCallback, ResultsCallback } from './types'
6+
import { SQLiteCloudRowset } from './rowset'
7+
import { SQLiteCloudConnection, DEFAULT_TIMEOUT, DEFAULT_PORT } from './connection'
8+
import { anonimizeError, anonimizeCommand } from './connection'
9+
10+
import { io, Socket, SocketOptions } from 'socket.io-client'
11+
12+
/**
13+
* Implementation of SQLiteCloudConnection that connects directly to the database via tls socket and raw, binary protocol.
14+
* SQLiteCloud low-level connection, will do messaging, handle socket, authentication, etc.
15+
* A connection socket is established when the connection is created and closed when the connection is closed.
16+
* All operations are serialized by waiting for any pending operations to complete. Once a connection is closed,
17+
* it cannot be reopened and you must create a new connection.
18+
*/
19+
export class SQLiteCloudWebsocketConnection extends SQLiteCloudConnection {
20+
/** Parse and validate provided connectionString or configuration */
21+
constructor(config: SQLiteCloudConfig | string, callback?: ErrorCallback) {
22+
super(config, callback)
23+
}
24+
25+
/** Currently opened tls socket used to communicated with SQLiteCloud server */
26+
private socket?: Socket
27+
28+
//
29+
// public properties
30+
//
31+
32+
/** True if connection is open */
33+
public get connected(): boolean {
34+
return !!this.socket
35+
}
36+
37+
//
38+
// private methods
39+
//
40+
41+
/* Opens a connection with the server and sends the initialization commands. Will throw in case of errors. */
42+
protected connect(callback?: ErrorCallback): this {
43+
this.operations.enqueue(done => {
44+
try {
45+
// connection established while we were waiting in line?
46+
console.assert(!this.connected, 'Connection already established')
47+
if (!this.socket) {
48+
const host = this.config.host as string
49+
// const connectionString = this.config.connectionString as string
50+
const gatewayUrl = 'ws://localhost:4000'
51+
//const gatewayUrl = `ws://${host}:4000`
52+
const connectionString = 'sqlitecloud://admin:uN3ARhdcKQ@og0wjec-m.sqlite.cloud:8860/chinook.db'
53+
this.socket = io(gatewayUrl, { auth: { token: connectionString } })
54+
}
55+
callback?.call(this, null)
56+
done(null)
57+
} catch (error) {
58+
callback?.call(this, error as Error)
59+
done(error as Error)
60+
}
61+
})
62+
return this
63+
}
64+
65+
/** Will send a command immediately (no queueing), return the rowset/result or throw an error */
66+
protected processCommands(commands: string, callback?: ResultsCallback): this {
67+
// connection needs to be established?
68+
if (!this.socket) {
69+
callback?.call(this, new SQLiteCloudError('Connection not established', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' }))
70+
return this
71+
}
72+
73+
this.socket.emit('v1/sql', { sql: commands, row: 'array' }, (response: any) => {
74+
if (response?.error) {
75+
const error = new SQLiteCloudError(response.error.detail, { ...response.error })
76+
callback?.call(this, error)
77+
} else {
78+
console.debug(`SQLiteCloudWebsocketConnection.processCommands - response: ${JSON.stringify(response)}`)
79+
const { data, metadata } = response
80+
if (data && metadata) {
81+
if (metadata.numberOfRows !== undefined && metadata.numberOfColumns !== undefined && metadata.columns !== undefined) {
82+
// we can recreate a SQLiteCloudRowset from the response
83+
const rowset = new SQLiteCloudRowset(metadata, data.flat())
84+
callback?.call(this, null, rowset)
85+
return
86+
}
87+
}
88+
callback?.call(this, null, response?.data)
89+
}
90+
})
91+
92+
return this
93+
}
94+
95+
//
96+
// public methods
97+
//
98+
99+
/** Disconnect from server, release connection. */
100+
public close(): this {
101+
console.assert(this.socket !== null, 'SQLiteCloudWsConnection.close - connection already closed')
102+
if (this.socket) {
103+
this.socket?.close()
104+
this.socket = undefined
105+
}
106+
this.operations.clear()
107+
this.socket = undefined
108+
return this
109+
}
110+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export { SQLiteCloudRowset, SQLiteCloudRow } from './rowset'
1010

1111
export { SQLiteCloudConnection } from './connection'
1212
export { SQLiteCloudTlsConnection } from './connection-tls'
13+
export { SQLiteCloudWebsocketConnection } from './connection-ws'
1314

1415
export { escapeSqlParameter, prepareSql } from './utilities'

test/connection.test.ts renamed to test/connection-tls.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* connection.test.ts - test low level communication protocol
2+
* connection-tls.test.ts - test low level communication protocol with tls sockets and raw commands
33
*/
44

55
import { SQLiteCloudTlsConnection } from '../src/index'
@@ -10,17 +10,17 @@ import {
1010
LONG_TIMEOUT,
1111
getTestingConfig,
1212
getChinookConfig,
13-
getChinookConnection,
13+
getChinookTlsConnection,
1414
// clearTestingDatabasesAsync,
1515
WARN_SPEED_MS,
1616
EXPECT_SPEED_MS
1717
} from './shared'
1818

19-
describe('connection', () => {
19+
describe('connection-tls', () => {
2020
let chinook: SQLiteCloudConnection
2121

2222
beforeEach(() => {
23-
chinook = getChinookConnection()
23+
chinook = getChinookTlsConnection()
2424
})
2525

2626
afterEach(() => {
@@ -265,7 +265,7 @@ describe('connection', () => {
265265
'should test chunked rowset',
266266
done => {
267267
// this operation sends 150 packets, so we need to increase the timeout
268-
const database = getChinookConnection(undefined, { timeout: 60 * 1000 })
268+
const database = getChinookTlsConnection(undefined, { timeout: 60 * 1000 })
269269
database.sendCommands('TEST ROWSET_CHUNK', (error, results) => {
270270
expect(error).toBeNull()
271271
expect(results.numberOfRows).toBe(147)
@@ -307,7 +307,7 @@ describe('connection', () => {
307307

308308
it('should apply short timeout', done => {
309309
// this operation sends 150 packets and cannot complete in 20ms
310-
const database = getChinookConnection(
310+
const database = getChinookTlsConnection(
311311
error => {
312312
if (error) {
313313
expect(error).toBeInstanceOf(SQLiteCloudError)

0 commit comments

Comments
 (0)