Awesome
gemini
Gemini protocol server & client.
Installation
npm install @derhuerst/gemini
Usage
Server
The following code assumes that you have a valid SSL certificate & key.
import {createServer, DEFAULT_PORT} from '@derhuerst/gemini'
const handleRequest = (req, res) => {
if (req.path === '/foo') {
if (!req.clientFingerprint) {
return res.requestTransientClientCert('/foo is secret!')
}
res.write('foo')
res.end('!')
} else if (req.path === '/bar') {
res.redirect('/foo')
} else {
res.gone()
}
}
const server = createServer({
cert: …, // certificate (+ chain)
key: …, // private key
passphrase: …, // passphrase, if the key is encrypted
}, handleRequest)
server.listen(DEFAULT_PORT)
server.on('error', console.error)
Client
import {sendGeminiRequest as request} from '@derhuerst/gemini/client.js'
request('/bar', (err, res) => {
if (err) {
console.error(err)
process.exit(1)
}
console.log(res.statusCode, res.statusMessage)
if (res.meta) console.log(res.meta)
res.pipe(process.stdout)
})
TOFU-style client certificates
Interactive clients for human users MUST inform users that such a session has been requested and require the user to approve generation of such a certificate. Transient certificates MUST NOT be generated automatically. – Gemini spec, section 1.4.3
This library leaves it up to you how to ask the user for approval. As an example, we're going to build a simple CLI prompt:
import {createInterface} from 'node:readline'
const letUserConfirmClientCertUsage = ({host, reason}, cb) => {
const prompt = createInterface({
input: process.stdin,
output: process.stdout,
})
prompt.question(`Send client cert to ${host}? Server says: "${reason}". y/n > `, (confirmed) => {
prompt.close()
cb(confirmed === 'y' || confirmed === 'Y')
})
}
request('/foo', {
// opt into client certificates
useClientCerts: true,
letUserConfirmClientCertUsage,
}, cb)
API
createServer
import {createGeminiServer as createServer} from '@derhuerst/gemini/server.js'
createServer(opt = {}, onRequest)
opt
extends the following defaults:
{
// SSL certificate & key
cert: null, key: null, passphrase: null,
// additional options to be passed into `tls.createServer`
tlsOpt: {},
// verify the ALPN ID requested by the client
// see https://de.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation
verifyAlpnId: alpnId => alpnId ? alpnId === ALPN_ID : true,
}
request
import {sendGeminiRequest as request} from '@derhuerst/gemini/client.js'
request(pathOrUrl, opt = {}, cb)
opt
extends the following defaults:
{
// follow redirects automatically
// Can also be a function `(nrOfRedirects, response) => boolean`.
followRedirects: false,
// client certificates
useClientCerts: false,
letUserConfirmClientCertUsage: null,
clientCertStore: defaultClientCertStore,
// time to wait for socket connection & TLS handshake
connectTimeout: 60 * 1000, // 60s
// time to wait for response headers *after* the socket is connected
headersTimeout: 30 * 1000, // 30s
// time to wait for the first byte of the response body *after* the socket is connected
timeout: 40 * 1000, // 40s
// additional options to be passed into `tls.connect`
tlsOpt: {},
// verify the ALPN ID chosen by the server
// see https://de.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation
verifyAlpnId: alpnId => alpnId ? (alpnId === ALPN_ID) : true,
}
connect
import {connectToGeminiServer as connect} from '@derhuerst/gemini/connect.js'
connect(opt = {}, cb)
opt
extends the following defaults:
{
hostname: '127.0.0.1',
port: 1965,
// client certificate
cert: null, key: null, passphrase: null,
// time to wait for socket connection & TLS handshake
connectTimeout: 60 * 1000, // 60s
// additional options to be passed into `tls.connect`
tlsOpt: {},
}
Related
gemini-fetch
– Load data from the Gemini protocol the way you would fetch from HTTP in JavaScriptdioscuri
– A gemtext (text/gemini
) parser with support for streaming, ASTs, and CSTs
Contributing
If you have a question or need support using gemini
, please double-check your code and setup first. If you think you have found a bug or want to propose a feature, use the issues page.