Skip to content

Commit 0dff616

Browse files
committed
Add deno-channel for non-encrypted connection and fix deno-backend
:
1 parent 6763bc8 commit 0dff616

File tree

5 files changed

+371
-43
lines changed

5 files changed

+371
-43
lines changed

packages/core/src/transaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class Transaction {
109109
this._lowRecordWatermak = lowRecordWatermark
110110
this._highRecordWatermark = highRecordWatermark
111111
this._bookmarks = Bookmarks.empty()
112+
this._acceptActive = () => { } // satisfy DenoJS
112113
this._activePromise = new Promise((resolve, reject) => {
113114
this._acceptActive = resolve
114115
})

packages/neo4j-driver-deno/generate.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ await Deno.writeTextFile(
164164
`export default "${version}" // Specified using --version when running generate.ts\n`,
165165
);
166166

167+
// Copy custom files
168+
await Deno.copyFile(
169+
"src/deno-channel.js",
170+
join(rootOutDir, "/bolt-connection/channel/browser/browser-channel.js"));
171+
172+
167173
////////////////////////////////////////////////////////////////////////////////
168174
// Warnings show up at the end
169175
if (!doTransform) {
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/**
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
/* eslint-env browser */
20+
import ChannelBuffer from '../channel-buf.js'
21+
import { newError, internal } from '../../../core/index.ts'
22+
23+
const {
24+
util: { ENCRYPTION_OFF, ENCRYPTION_ON }
25+
} = internal
26+
27+
let _CONNECTION_IDGEN = 0
28+
/**
29+
* Create a new DenoChannel to be used in web browsers.
30+
* @access private
31+
*/
32+
export default class DenoChannel {
33+
/**
34+
* Create new instance
35+
* @param {ChannelConfig} config - configuration for this channel.
36+
* @param {function(): string} protocolSupplier - function that detects protocol of the web page. Should only be used in tests.
37+
*/
38+
constructor (
39+
config
40+
) {
41+
this.id = _CONNECTION_IDGEN++
42+
this._conn = null
43+
this._pending = []
44+
this._error = null
45+
this._open = true
46+
this._config = config
47+
48+
this._receiveTimeout = null
49+
this._receiveTimeoutStarted = false
50+
this._receiveTimeoutId = null
51+
52+
this._connectionErrorCode = config.connectionErrorCode
53+
this._handleConnectionError = this._handleConnectionError.bind(this)
54+
this._handleConnectionTerminated = this._handleConnectionTerminated.bind(
55+
this
56+
)
57+
58+
this._socketPromise = Deno.connect({
59+
hostname: config.address.host(),
60+
port: config.address.port()
61+
})
62+
.then(conn => {
63+
this._clearConnectionTimeout()
64+
if (!this._open) {
65+
return conn.close()
66+
}
67+
this._conn = conn
68+
69+
setupReader(this)
70+
.catch(this._handleConnectionError)
71+
72+
const pending = this._pending
73+
this._pending = null
74+
for (let i = 0; i < pending.length; i++) {
75+
this.write(pending[i])
76+
}
77+
})
78+
.catch(this._handleConnectionError)
79+
80+
this._connectionTimeoutFired = false
81+
this._connectionTimeoutId = this._setupConnectionTimeout()
82+
}
83+
84+
_setupConnectionTimeout () {
85+
const timeout = this._config.connectionTimeout
86+
if (timeout) {
87+
return setTimeout(() => {
88+
this._connectionTimeoutFired = true
89+
this.close()
90+
.catch(this._handleConnectionError)
91+
}, timeout)
92+
}
93+
return null
94+
}
95+
96+
/**
97+
* Remove active connection timeout, if any.
98+
* @private
99+
*/
100+
_clearConnectionTimeout () {
101+
const timeoutId = this._connectionTimeoutId
102+
if (timeoutId !== null) {
103+
this._connectionTimeoutFired = false
104+
this._connectionTimeoutId = null
105+
clearTimeout(timeoutId)
106+
}
107+
}
108+
109+
_handleConnectionError (err) {
110+
let msg =
111+
'Failed to connect to server. ' +
112+
'Please ensure that your database is listening on the correct host and port ' +
113+
'and that you have compatible encryption settings both on Neo4j server and driver. ' +
114+
'Note that the default encryption setting has changed in Neo4j 4.0.'
115+
if (err.message) msg += ' Caused by: ' + err.message
116+
this._error = newError(msg, this._connectionErrorCode)
117+
if (this.onerror) {
118+
this.onerror(this._error)
119+
}
120+
}
121+
122+
_handleConnectionTerminated () {
123+
this._open = false
124+
this._error = newError(
125+
'Connection was closed by server',
126+
this._connectionErrorCode
127+
)
128+
if (this.onerror) {
129+
this.onerror(this._error)
130+
}
131+
}
132+
133+
134+
/**
135+
* Write the passed in buffer to connection
136+
* @param {ChannelBuffer} buffer - Buffer to write
137+
*/
138+
write (buffer) {
139+
if (this._pending !== null) {
140+
this._pending.push(buffer)
141+
} else if (buffer instanceof ChannelBuffer) {
142+
this._conn.write(buffer._buffer)
143+
} else {
144+
throw newError("Don't know how to send buffer: " + buffer)
145+
}
146+
}
147+
148+
/**
149+
* Close the connection
150+
* @returns {Promise} A promise that will be resolved after channel is closed
151+
*/
152+
async close () {
153+
if (this._open) {
154+
this._open = false
155+
if (this._conn != null) {
156+
await this._conn.close()
157+
}
158+
}
159+
}
160+
161+
/**
162+
* Setup the receive timeout for the channel.
163+
*
164+
* Not supported for the browser channel.
165+
*
166+
* @param {number} receiveTimeout The amount of time the channel will keep without receive any data before timeout (ms)
167+
* @returns {void}
168+
*/
169+
setupReceiveTimeout (receiveTimeout) {
170+
this._receiveTimeout = receiveTimeout
171+
}
172+
173+
/**
174+
* Stops the receive timeout for the channel.
175+
*/
176+
stopReceiveTimeout () {
177+
if (this._receiveTimeout !== null && this._receiveTimeoutStarted) {
178+
this._receiveTimeoutStarted = false
179+
if (this._receiveTimeoutId != null) {
180+
clearTimeout(this._receiveTimeoutId)
181+
}
182+
this._receiveTimeoutId = null
183+
}
184+
}
185+
186+
/**
187+
* Start the receive timeout for the channel.
188+
*/
189+
startReceiveTimeout () {
190+
if (this._receiveTimeout !== null && !this._receiveTimeoutStarted) {
191+
this._receiveTimeoutStarted = true
192+
this._resetTimeout()
193+
}
194+
}
195+
196+
_resetTimeout () {
197+
if (!this._receiveTimeoutStarted) {
198+
return
199+
}
200+
201+
if (this._receiveTimeoutId !== null) {
202+
clearTimeout(this._receiveTimeoutId)
203+
}
204+
205+
this._receiveTimeoutId = setTimeout(() => {
206+
this._receiveTimeoutId = null
207+
this._timedout = true
208+
this.stopReceiveTimeout()
209+
this._error = newError(
210+
`Connection lost. Server didn't respond in ${this._receiveTimeout}ms`,
211+
this._config.connectionErrorCode
212+
)
213+
214+
this.close()
215+
if (this.onerror) {
216+
this.onerror(this._error)
217+
}
218+
}, this._receiveTimeout)
219+
}
220+
}
221+
222+
async function setupReader (channel) {
223+
try {
224+
for await (const message of Deno.iter(channel._conn)) {
225+
channel._resetTimeout()
226+
227+
if (!channel._open) {
228+
return
229+
}
230+
if (channel.onmessage) {
231+
channel.onmessage(new ChannelBuffer(message))
232+
}
233+
}
234+
channel._handleConnectionTerminated()
235+
} catch (error) {
236+
if (channel._open) {
237+
channel._handleConnectionError(error)
238+
}
239+
}
240+
}
241+
242+
/**
243+
* @param {ChannelConfig} config - configuration for the channel.
244+
* @return {boolean} `true` if encryption enabled in the config, `false` otherwise.
245+
*/
246+
function isEncryptionExplicitlyTurnedOn (config) {
247+
return config.encrypted === true || config.encrypted === ENCRYPTION_ON
248+
}
249+
250+
/**
251+
* @param {ChannelConfig} config - configuration for the channel.
252+
* @return {boolean} `true` if encryption disabled in the config, `false` otherwise.
253+
*/
254+
function isEncryptionExplicitlyTurnedOff (config) {
255+
return config.encrypted === false || config.encrypted === ENCRYPTION_OFF
256+
}
257+
258+
/**
259+
* @param {function(): string} protocolSupplier - function that detects protocol of the web page.
260+
* @return {boolean} `true` if protocol returned by the given function is secure, `false` otherwise.
261+
*/
262+
function isProtocolSecure (protocolSupplier) {
263+
const protocol =
264+
typeof protocolSupplier === 'function' ? protocolSupplier() : ''
265+
return protocol && protocol.toLowerCase().indexOf('https') >= 0
266+
}
267+
268+
function verifyEncryptionSettings (encryptionOn, encryptionOff, secureProtocol) {
269+
if (secureProtocol === null) {
270+
// do nothing sice the protocol could not be identified
271+
} else if (encryptionOn && !secureProtocol) {
272+
// encryption explicitly turned on for a driver used on a HTTP web page
273+
console.warn(
274+
'Neo4j driver is configured to use secure WebSocket on a HTTP web page. ' +
275+
'WebSockets might not work in a mixed content environment. ' +
276+
'Please consider configuring driver to not use encryption.'
277+
)
278+
} else if (encryptionOff && secureProtocol) {
279+
// encryption explicitly turned off for a driver used on a HTTPS web page
280+
console.warn(
281+
'Neo4j driver is configured to use insecure WebSocket on a HTTPS web page. ' +
282+
'WebSockets might not work in a mixed content environment. ' +
283+
'Please consider configuring driver to use encryption.'
284+
)
285+
}
286+
}

0 commit comments

Comments
 (0)