Skip to content

Commit

Permalink
Support for Cloudflare Workers & Pages (#599)
Browse files Browse the repository at this point in the history
* Initial support for cloudflare

* Types here are not needed

* Include cloudflare in npm

* Allow crypto to be async to support WebCrypto polyfills

* Polyfill crypto with WebCrypto for cloudflare

* Use crypto polyfill for cloudflare

* Not ready for tests on CF yet

* build

* build cf

* build

* README.md - improve the "Multiple statements in one query" section

- add links for the official documentation
- escape the backtick character
- change the subtitle to "await sql``.simple()" instead of "await sql`select 1; select 2;`.simple()" (to be coherent with the other subtitles)
- add a small example below

* Ensure number options are coerced from string - fixes #622

* Add sql.reserve method

* build

* create beginPrepared function (#628)

* create beginPrepared function

* change implementation to new method

* add prepare method type to TransactionSql

* add documentations and test

* fix test

* enable prepared transactions in the bootstrap script

* enable prepared transactions in the github actions setup file

* fix github actions

* fix github actions yml file

* Please the linter

* build

* Fix for using compatibility_flags = [ "nodejs_compat" ] instead

* build

* please eslint

* draft: Cloudflare works ! 🎉  (#618)

* Reworked from source cloudflare branch
feat: reran transpile
fix linter
feat: final touches + test files

squashed 2 commits
fix: Polyfills bulk (to please linter)
fix: Removed MD5 + put back SHA in the digest()

squashed 5 commits
fix: cloudflare workers deployment
feat: fixed auth
fix: encrypt not found in worker :(
fix: postgres SASL
fix: linting

* fix: merge cleanup

---------

Co-authored-by: wackfx <hello@wackfx.com>

* Switch to performance.now

* Please the linter

* Don't collect polyfills (keep line numbers similar to src)

* Simplify manual test script

* build

---------

Co-authored-by: Paulo Vieira <paulovieira@gmail.com>
Co-authored-by: Shayan Shojaei <68788931+shayan-shojaei@users.noreply.github.com>
Co-authored-by: Wack <135170502+wackfx@users.noreply.github.com>
Co-authored-by: wackfx <hello@wackfx.com>
  • Loading branch information
5 people committed Jul 5, 2023
1 parent 94f7228 commit b88e261
Show file tree
Hide file tree
Showing 18 changed files with 2,996 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
],
"max-len": [
2,
120
150
],
"max-nested-callbacks": [
2,
Expand Down
218 changes: 218 additions & 0 deletions cf/polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { EventEmitter } from 'node:events'
import { Buffer } from 'node:buffer'

const Crypto = globalThis.crypto

let ids = 1
const tasks = new Set()

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Reg = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Reg = new RegExp(
'^(' +
`(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
`(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
`(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
`(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
`(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
`(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
`(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
`(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
')(%[0-9a-zA-Z-.:]{1,})?$'
)

const textEncoder = new TextEncoder()
export const crypto = {
randomBytes: l => Crypto.getRandomValues(Buffer.alloc(l)),
pbkdf2Sync: async(password, salt, iterations, keylen) =>
Crypto.subtle.deriveBits(
{
name: 'PBKDF2',
hash: 'SHA-256',
salt,
iterations
},
await Crypto.subtle.importKey(
'raw',
textEncoder.encode(password),
'PBKDF2',
false,
['deriveBits']
),
keylen * 8,
['deriveBits']
),
createHash: type => ({
update: x => ({
digest: () => {
if (type !== 'sha256')
throw Error('createHash only supports sha256 on cloudflare.')
if (!(x instanceof Uint8Array))
x = textEncoder.encode(x)
return Crypto.subtle.digest('SHA-256', x)
}
})
}),
createHmac: (type, key) => ({
update: x => ({
digest: async() =>
Buffer.from(
await Crypto.subtle.sign(
'HMAC',
await Crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']),
textEncoder.encode(x)
)
)
})
})
}

export const process = {
env: {}
}

export const os = {
userInfo() {
return { username: 'postgres' }
}
}

export const fs = {
readFile() {
throw new Error('Reading files not supported on CloudFlare')
}
}

export const net = {
isIP: (x) => RegExp.prototype.test.call(IPv4Reg, x) ? 4 : RegExp.prototype.test.call(IPv6Reg, x) ? 6 : 0,
Socket
}

export { setImmediate, clearImmediate }

export const tls = {
connect({ socket: tcp, servername }) {
tcp.writer.releaseLock()
tcp.reader.releaseLock()
tcp.readyState = 'upgrading'
tcp.raw = tcp.raw.startTls({ servername })
tcp.raw.closed.then(
() => tcp.emit('close'),
(e) => tcp.emit('error', e)
)
tcp.writer = tcp.raw.writable.getWriter()
tcp.reader = tcp.raw.readable.getReader()

tcp.writer.ready.then(() => {
tcp.read()
tcp.readyState = 'upgrade'
})
return tcp
}
}

function Socket() {
const tcp = Object.assign(new EventEmitter(), {
readyState: 'open',
raw: null,
writer: null,
reader: null,
connect,
write,
end,
destroy,
read
})

return tcp

async function connect(port, host) {
try {
tcp.readyState = 'opening'
const { connect } = await import('cloudflare:sockets')
tcp.raw = connect(host + ':' + port, tcp.ssl ? { secureTransport: 'starttls' } : {})
tcp.raw.closed.then(
() => {
tcp.readyState !== 'upgrade'
? close()
: ((tcp.readyState = 'open'), tcp.emit('secureConnect'))
},
(e) => tcp.emit('error', e)
)
tcp.writer = tcp.raw.writable.getWriter()
tcp.reader = tcp.raw.readable.getReader()

tcp.ssl ? readFirst() : read()
tcp.writer.ready.then(() => {
tcp.readyState = 'open'
tcp.emit('connect')
})
} catch (err) {
error(err)
}
}

function close() {
if (tcp.readyState === 'closed')
return

tcp.readyState = 'closed'
tcp.emit('close')
}

function write(data, cb) {
tcp.writer.write(data).then(cb, error)
return true
}

function end(data) {
return data
? tcp.write(data, () => tcp.raw.close())
: tcp.raw.close()
}

function destroy() {
tcp.destroyed = true
tcp.end()
}

async function read() {
try {
let done
, value
while (({ done, value } = await tcp.reader.read(), !done))
tcp.emit('data', Buffer.from(value))
} catch (err) {
error(err)
}
}

async function readFirst() {
const { value } = await tcp.reader.read()
tcp.emit('data', Buffer.from(value))
}

function error(err) {
tcp.emit('error', err)
tcp.emit('close')
}
}

function setImmediate(fn) {
const id = ids++
tasks.add(id)
queueMicrotask(() => {
if (tasks.has(id)) {
fn()
tasks.delete(id)
}
})
return id
}

function clearImmediate(id) {
tasks.delete(id)
}
79 changes: 79 additions & 0 deletions cf/src/bytes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Buffer } from 'node:buffer'
const size = 256
let buffer = Buffer.allocUnsafe(size)

const messages = 'BCcDdEFfHPpQSX'.split('').reduce((acc, x) => {
const v = x.charCodeAt(0)
acc[x] = () => {
buffer[0] = v
b.i = 5
return b
}
return acc
}, {})

const b = Object.assign(reset, messages, {
N: String.fromCharCode(0),
i: 0,
inc(x) {
b.i += x
return b
},
str(x) {
const length = Buffer.byteLength(x)
fit(length)
b.i += buffer.write(x, b.i, length, 'utf8')
return b
},
i16(x) {
fit(2)
buffer.writeUInt16BE(x, b.i)
b.i += 2
return b
},
i32(x, i) {
if (i || i === 0) {
buffer.writeUInt32BE(x, i)
return b
}
fit(4)
buffer.writeUInt32BE(x, b.i)
b.i += 4
return b
},
z(x) {
fit(x)
buffer.fill(0, b.i, b.i + x)
b.i += x
return b
},
raw(x) {
buffer = Buffer.concat([buffer.subarray(0, b.i), x])
b.i = buffer.length
return b
},
end(at = 1) {
buffer.writeUInt32BE(b.i - at, at)
const out = buffer.subarray(0, b.i)
b.i = 0
buffer = Buffer.allocUnsafe(size)
return out
}
})

export default b

function fit(x) {
if (buffer.length - b.i < x) {
const prev = buffer
, length = prev.length

buffer = Buffer.allocUnsafe(length + (length >> 1) + x)
prev.copy(buffer)
}
}

function reset() {
b.i = 0
return b
}
Loading

0 comments on commit b88e261

Please sign in to comment.