{"id":13770327,"url":"https://github.com/derhuerst/gemini","last_synced_at":"2025-04-06T13:10:14.068Z","repository":{"id":43738885,"uuid":"260705342","full_name":"derhuerst/gemini","owner":"derhuerst","description":"Gemini protocol server \u0026 client for Node.js.","archived":false,"fork":false,"pushed_at":"2025-03-19T23:20:36.000Z","size":143,"stargazers_count":51,"open_issues_count":4,"forks_count":8,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-28T16:21:19.570Z","etag":null,"topics":["gemini","protocol","server"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/derhuerst.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-05-02T14:32:45.000Z","updated_at":"2025-03-19T23:19:00.000Z","dependencies_parsed_at":"2024-01-12T12:19:54.970Z","dependency_job_id":null,"html_url":"https://github.com/derhuerst/gemini","commit_stats":{"total_commits":42,"total_committers":5,"mean_commits":8.4,"dds":0.1428571428571429,"last_synced_commit":"873b457889def5c9435ae943283a5f09f275a62e"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fgemini","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fgemini/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fgemini/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fgemini/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/derhuerst","download_url":"https://codeload.github.com/derhuerst/gemini/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247485287,"owners_count":20946398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gemini","protocol","server"],"created_at":"2024-08-03T17:00:36.271Z","updated_at":"2025-04-06T13:10:14.062Z","avatar_url":"https://github.com/derhuerst.png","language":"JavaScript","funding_links":["https://github.com/sponsors/derhuerst"],"categories":["Programming","JavaScript"],"sub_categories":["Graphical"],"readme":"# gemini\n\n**[Gemini protocol](https://geminiprotocol.net/) server \u0026 client.**\n\n[![npm version](https://img.shields.io/npm/v/@derhuerst/gemini.svg)](https://www.npmjs.com/package/@derhuerst/gemini)\n![ISC-licensed](https://img.shields.io/github/license/derhuerst/gemini.svg)\n![minimum Node.js version](https://img.shields.io/node/v/@derhuerst/gemini.svg)\n[![support me via GitHub Sponsors](https://img.shields.io/badge/support%20me-donate-fa7664.svg)](https://github.com/sponsors/derhuerst)\n[![chat with me on Twitter](https://img.shields.io/badge/chat%20with%20me-on%20Twitter-1da1f2.svg)](https://twitter.com/derhuerst)\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e This package implements the Gemini specification as of end of 2022.\n\n\n## Installation\n\n```shell\nnpm install @derhuerst/gemini\n```\n\n\n## Usage\n\n### Server\n\nThe following code assumes that you have a valid SSL certificate \u0026 key.\n\n```js\nimport {createServer, DEFAULT_PORT} from '@derhuerst/gemini'\n\nconst handleRequest = (req, res) =\u003e {\n\tif (req.path === '/foo') {\n\t\tif (!req.clientFingerprint) {\n\t\t\treturn res.requestTransientClientCert('/foo is secret!')\n\t\t}\n\t\tres.write('foo')\n\t\tres.end('!')\n\t} else if (req.path === '/bar') {\n\t\tres.redirect('/foo')\n\t} else {\n\t\tres.gone()\n\t}\n}\n\nconst server = createServer({\n\tcert: …, // certificate (+ chain)\n\tkey: …, // private key\n\tpassphrase: …, // passphrase, if the key is encrypted\n}, handleRequest)\n\nserver.listen(DEFAULT_PORT)\nserver.on('error', console.error)\n```\n\n### Client\n\n```js\nimport {sendGeminiRequest as request} from '@derhuerst/gemini/client.js'\n\nrequest('/bar', (err, res) =\u003e {\n\tif (err) {\n\t\tconsole.error(err)\n\t\tprocess.exit(1)\n\t}\n\n\tconsole.log(res.statusCode, res.statusMessage)\n\tif (res.meta) console.log(res.meta)\n\tres.pipe(process.stdout)\n})\n```\n\n#### [TOFU](https://en.wikipedia.org/wiki/Trust_on_first_use)-style client certificates\n\n\u003e 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.\n– [Gemini spec](https://gemini.circumlunar.space/docs/spec-spec.txt), section 1.4.3\n\nThis 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:\n\n```js\nimport {createInterface} from 'node:readline'\n\nconst letUserConfirmClientCertUsage = ({host, reason}, cb) =\u003e {\n\tconst prompt = createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t})\n\tprompt.question(`Send client cert to ${host}? Server says: \"${reason}\". y/n \u003e `, (confirmed) =\u003e {\n\t\tprompt.close()\n\t\tcb(confirmed === 'y' || confirmed === 'Y')\n\t})\n}\n\nrequest('/foo', {\n\t// opt into client certificates\n\tuseClientCerts: true,\n\tletUserConfirmClientCertUsage,\n}, cb)\n```\n\n\n## API\n\n### `createServer`\n\n```js\nimport {createGeminiServer as createServer} from '@derhuerst/gemini/server.js'\ncreateServer(opt = {}, onRequest)\n```\n\n`opt` extends the following defaults:\n\n```js\n{\n\t// SSL certificate \u0026 key\n\tcert: null, key: null, passphrase: null,\n\t// additional options to be passed into `tls.createServer`\n\ttlsOpt: {},\n\t// verify the ALPN ID requested by the client\n\t// see https://de.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation\n\tverifyAlpnId: alpnId =\u003e alpnId ? alpnId === ALPN_ID : true,\n}\n```\n\n### `request`\n\n```js\nimport {sendGeminiRequest as request} from '@derhuerst/gemini/client.js'\nrequest(pathOrUrl, opt = {}, cb)\n```\n\n`opt` extends the following defaults:\n\n```js\n{\n\t// follow redirects automatically\n\t// Can also be a function `(nrOfRedirects, response) =\u003e boolean`.\n\tfollowRedirects: false,\n\t// client certificates\n\tuseClientCerts: false,\n\tletUserConfirmClientCertUsage: null,\n\tclientCertStore: defaultClientCertStore,\n\t// time to wait for socket connection \u0026 TLS handshake\n\tconnectTimeout: 60 * 1000, // 60s\n\t// time to wait for response headers *after* the socket is connected\n\theadersTimeout: 30 * 1000, // 30s\n\t// time to wait for the first byte of the response body *after* the socket is connected\n\ttimeout: 40 * 1000, // 40s\n\t// additional options to be passed into `tls.connect`\n\ttlsOpt: {},\n\t// verify the ALPN ID chosen by the server\n\t// see https://de.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation\n\tverifyAlpnId: alpnId =\u003e alpnId ? (alpnId === ALPN_ID) : true,\n}\n```\n\n### `connect`\n\n```js\nimport {connectToGeminiServer as connect} from '@derhuerst/gemini/connect.js'\nconnect(opt = {}, cb)\n```\n\n`opt` extends the following defaults:\n\n```js\n{\n\thostname: '127.0.0.1',\n\tport: 1965,\n\t// client certificate\n\tcert: null, key: null, passphrase: null,\n\t// time to wait for socket connection \u0026 TLS handshake\n\tconnectTimeout: 60 * 1000, // 60s\n\t// additional options to be passed into `tls.connect`\n\ttlsOpt: {},\n}\n```\n\n\n## Related\n\n- [`gemini-fetch`](https://github.com/RangerMauve/gemini-fetch) – Load data from the Gemini protocol the way you would fetch from HTTP in JavaScript\n- [`dioscuri`](https://github.com/wooorm/dioscuri) – A gemtext (`text/gemini`) parser with support for streaming, ASTs, and CSTs\n\n\n## Contributing\n\nIf 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](https://github.com/derhuerst/gemini/issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderhuerst%2Fgemini","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fderhuerst%2Fgemini","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderhuerst%2Fgemini/lists"}