33 */
44
55import { io , Socket } from 'socket.io-client'
6+ import { Decoder as SocketIODecoder , Encoder as SocketIOEncoder } from 'socket.io-parser'
67import { SQLiteCloudConnection } from './connection'
78import { SQLiteCloudRowset } from './rowset'
8- import { ErrorCallback , ResultsCallback , SQLiteCloudCommand , SQLiteCloudConfig , SQLiteCloudError } from './types'
9- import { decodeBigIntMarkers , encodeBigIntMarkers } from './utilities'
9+ import {
10+ DEFAULT_WEBSOCKET_BLOB_TRANSFER_FORMAT ,
11+ ErrorCallback ,
12+ ResultsCallback ,
13+ SQLiteCloudCommand ,
14+ SQLiteCloudConfig ,
15+ SQLiteCloudError ,
16+ SQLiteCloudWebsocketBlobTransferFormat
17+ } from './types'
18+ import { decodeBigIntMarkers , decodeWebsocketRowsetData , encodeBigIntMarkers , parseWebsocketBlobTransferFormat , parseWebsocketMaxAttachments } from './utilities'
19+
20+ const SocketIODecoderBase = SocketIODecoder as unknown as new ( ...args : any [ ] ) => { opts ?: { maxAttachments ?: number } }
21+
22+ function createSocketIOParser ( maxAttachments : number ) {
23+ class SQLiteCloudSocketIODecoder extends SocketIODecoderBase {
24+ constructor ( opts ?: any ) {
25+ const decoderOptions = typeof opts === 'function' ? { reviver : opts } : opts
26+ super ( decoderOptions ?. reviver )
27+ this . opts ||= { }
28+ this . opts . maxAttachments = Math . max ( this . opts . maxAttachments ?? decoderOptions ?. maxAttachments ?? 0 , maxAttachments )
29+ }
30+ }
31+
32+ return {
33+ Encoder : SocketIOEncoder ,
34+ Decoder : SQLiteCloudSocketIODecoder
35+ }
36+ }
37+
38+ function getResponseBlobTransferFormat ( response : any ) : SQLiteCloudWebsocketBlobTransferFormat | undefined {
39+ return parseWebsocketBlobTransferFormat ( response ?. capabilities ?. blobTransferFormat || response ?. blobTransferFormat , undefined )
40+ }
41+
42+ function getAttachmentLimitError ( description : unknown , limit : number ) : SQLiteCloudError | undefined {
43+ const descriptionMessage = description instanceof Error ? description . message : typeof description === 'string' ? description : ''
44+ if ( / i l l e g a l a t t a c h m e n t s / i. test ( descriptionMessage ) ) {
45+ return new SQLiteCloudError (
46+ `WebSocket blob response exceeded the configured Socket.IO attachment limit (${ limit } ). Use websocketBlobFormat=base64-blobs-v1 or increase websocketMaxAttachments.` ,
47+ {
48+ errorCode : 'ERR_WEBSOCKET_MAX_ATTACHMENTS_EXCEEDED' ,
49+ cause : description as Error | string
50+ }
51+ )
52+ }
53+ }
1054
1155/**
1256 * Implementation of TransportConnection that connects to the database indirectly
@@ -31,20 +75,33 @@ export class SQLiteCloudWebsocketConnection extends SQLiteCloudConnection {
3175 if ( ! this . socket ) {
3276 this . config = config
3377 const connectionstring = this . config . connectionstring as string
78+ const websocketMaxAttachments = parseWebsocketMaxAttachments ( this . config . websocketMaxAttachments )
3479 const gatewayUrl = this . config ?. gatewayurl || `${ this . config . host === 'localhost' ? 'ws' : 'wss' } ://${ this . config . host as string } :443`
35- this . socket = io ( gatewayUrl , { auth : { token : connectionstring } } )
80+ this . socket = io ( gatewayUrl , { auth : { token : connectionstring } , parser : createSocketIOParser ( websocketMaxAttachments ) } )
3681
3782 this . socket . on ( 'connect' , ( ) => {
3883 callback ?. call ( this , null )
3984 } )
4085
41- this . socket . on ( 'disconnect' , reason => {
86+ this . socket . on ( 'disconnect' , ( reason , description ) => {
4287 this . close ( )
43- callback ?. call ( this , new SQLiteCloudError ( 'Disconnected' , { errorCode : 'ERR_CONNECTION_ENDED' , cause : reason } ) )
88+ callback ?. call (
89+ this ,
90+ ( reason === 'parse error' && getAttachmentLimitError ( description , websocketMaxAttachments ) ) ||
91+ new SQLiteCloudError ( 'Disconnected' , { errorCode : 'ERR_CONNECTION_ENDED' , cause : reason } )
92+ )
4493 } )
4594
4695 this . socket . on ( 'connect_error' , ( error : any ) => {
4796 this . close ( )
97+ if ( error ?. message === 'parse error' || error ?. cause === 'parse error' ) {
98+ callback ?. call (
99+ this ,
100+ getAttachmentLimitError ( error ?. description || error ?. data || error ?. cause , websocketMaxAttachments ) ||
101+ new SQLiteCloudError ( 'Connection error' , { errorCode : 'ERR_CONNECTION_ERROR' , cause : error } )
102+ )
103+ return
104+ }
48105 let message = error . message || 'Connection error'
49106 if ( typeof error . context == 'object' && error . context . responseText ) {
50107 try {
@@ -87,16 +144,24 @@ export class SQLiteCloudWebsocketConnection extends SQLiteCloudConnection {
87144 sql : commands . query ,
88145 bind : encodeBigIntMarkers ( commands . parameters ) ,
89146 row : 'array' ,
90- safe_integer_mode : this . config . safe_integer_mode
147+ safe_integer_mode : this . config . safe_integer_mode ,
148+ capabilities : {
149+ blobTransferFormat : this . config . websocketBlobFormat || DEFAULT_WEBSOCKET_BLOB_TRANSFER_FORMAT
150+ }
91151 } ,
92152 ( response : any ) => {
93153 if ( response ?. error ) {
94154 const error = new SQLiteCloudError ( response . error . detail , { ...response . error } )
95155 callback ?. call ( this , error )
96156 } else {
97157 const { metadata } = response
98- const data = decodeBigIntMarkers ( response ?. data , this . config . safe_integer_mode )
99- if ( data && metadata ) {
158+ const blobTransferFormat = getResponseBlobTransferFormat ( response )
159+ const data =
160+ metadata && metadata . numberOfRows !== undefined && metadata . numberOfColumns !== undefined && metadata . columns !== undefined
161+ ? decodeWebsocketRowsetData ( response ?. data , metadata , this . config . safe_integer_mode , blobTransferFormat )
162+ : decodeBigIntMarkers ( response ?. data , this . config . safe_integer_mode )
163+
164+ if ( data !== undefined && metadata ) {
100165 if ( metadata . numberOfRows !== undefined && metadata . numberOfColumns !== undefined && metadata . columns !== undefined ) {
101166 console . assert ( Array . isArray ( data ) , 'SQLiteCloudWebsocketConnection.transportCommands - data is not an array' )
102167 // we can recreate a SQLiteCloudRowset from the response which we know to be an array of arrays
0 commit comments