@derhuerst/gemini v2.0.1
gemini
Gemini protocol server & client.
This package implements the Gemini specification as of end of 2022.
Installation
npm install @derhuerst/geminiUsage
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 JavaScript
- dioscuri– 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.