Compare commits
5 Commits
7761c18fd5
...
6a412a2dec
Author | SHA1 | Date |
---|---|---|
Matthias Portzel | 6a412a2dec | |
Matthias Portzel | c5f4d281d5 | |
Matthias Portzel | 3063358ecf | |
MatthiasSaihttam | 34ef03a43c | |
MatthiasSaihttam | 2dabbc0969 |
|
@ -1 +1,2 @@
|
|||
*.pem
|
||||
node_modules
|
||||
|
|
|
@ -17,10 +17,10 @@ function wildHostMatches(wildHost, hostInstance) {
|
|||
if (wildHost[0] === "*") {
|
||||
//Return if everything after the first . matches
|
||||
//TODO: what happens if I'm dumb and enter *ww.example.com
|
||||
return wildHost.slice(wildHost.indexOf(".")) === hostInstance.slice(hostInstance.indexOf("."))
|
||||
return wildHost.toLowerCase().slice(wildHost.indexOf(".")) === hostInstance.toLowerCase().slice(hostInstance.indexOf("."))
|
||||
}else {
|
||||
//If there's no wildcard, just return if they match
|
||||
return wildHost === hostInstance;
|
||||
return wildHost.toLowerCase() === hostInstance.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,9 @@ export default class GeminiServer {
|
|||
|
||||
if (matches) {
|
||||
Promise.resolve(p.handler.handle(url, p.basePath, socket)).then(res => {
|
||||
socket.write(res);
|
||||
if (res) {
|
||||
socket.write(res);
|
||||
}
|
||||
socket.end();
|
||||
}).catch(console.error);
|
||||
return;
|
||||
|
@ -220,6 +222,9 @@ export default class GeminiServer {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This can be just ECONNRESET
|
||||
socket.on("error", console.error);
|
||||
});
|
||||
server.listen(1965);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
The software is provided "as is", without warranty of any kind,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose and noninfringement.
|
||||
In no event shall the authors be liable for any claim, damages or
|
||||
other liability, whether in an action of contract, tort or otherwise,
|
||||
arising from, out of or in connection with the software or the use or
|
||||
other dealings in the software.
|
|
@ -1,3 +1,15 @@
|
|||
# Astronomical Theater
|
||||
A Node.js Gemini server and proxy.
|
||||
|
||||
## Do not use.
|
||||
This is known to be buggy.
|
||||
I use it because the promise of Gemini is that people are able to write their own software, and that's an ideal that I believe in.
|
||||
And I open-source it because I believe code should be free.
|
||||
|
||||
### Known bugs
|
||||
* It reads the certificates into memory at start up, and never again. This means that if you (like me) use Let's Encrypt and rotate certs, then after three months the server needs to be restarted.
|
||||
* It suffers from a Node JS bug which causes it to allocate ~10.8GB of virtual memory (not actual memory so this theoretically doesn't matter).
|
||||
* Most importantly, it sometimes drops requests that it's proxying before they've completed. I kind of suspect this is also a Node.js bug, but I don't have a minimum reproduction case.
|
||||
|
||||
### We require SNI!
|
||||
The Gemini spec requires clients to implement SNI. This server requires SNI to connect.
|
||||
|
@ -23,7 +35,7 @@ A `relativePath` is the difference between a `basePath` and a `urlPath`. It's no
|
|||
|
||||
Sometimes these paths will include their hostnames
|
||||
|
||||
A path in the pathRegistry is a `p`, which is an object with a hostname, a basePath, and a handler
|
||||
A path in the pathRegistry is a `p`, which is an object with a hostname, a basePath, and a handler
|
||||
|
||||
|
||||
|
||||
|
@ -37,6 +49,12 @@ openssl genrsa -out private-key.pem 2048
|
|||
openssl req -new -sha256 -key private-key.pem -out csr.pem
|
||||
# Self-sign, generating cert
|
||||
openssl x509 -req -in csr.pem -signkey private-key.pem -out public-cert.pem
|
||||
|
||||
# For debugging clients, it can be useful to start a openssl server with these certs:
|
||||
openssl s_server -key private-key.pem -cert public-cert.pem -accept 1965
|
||||
|
||||
# And of course creating a client with openssl. The -servername is needed for SNI
|
||||
openssl s_client -connect example.com:1965 -servername example.com
|
||||
```
|
||||
|
||||
|
||||
|
@ -44,4 +62,4 @@ openssl x509 -req -in csr.pem -signkey private-key.pem -out public-cert.pem
|
|||
It's possible to imagine a situation where astronomical theater is behind a proxy and the proxy doesn't do address translation, passing the raw gemini request to us.
|
||||
In this case, our behavior is undefined.
|
||||
|
||||
Similarly, SNI is not supported with IP addresses.
|
||||
Similarly, SNI is not supported with IP addresses.
|
|
@ -4,6 +4,8 @@ import DefaultHandler from "./handlers/default.js";
|
|||
import ReverseProxyHandler from "./handlers/revproxy.js";
|
||||
|
||||
// import {, staticFileHandler} from "./main.js";
|
||||
// If you'r going to be reverse-proxying a server with a self-signed cert, you need
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
|
||||
|
||||
const server = new GeminiServer({
|
||||
// port: 1965
|
||||
|
@ -57,8 +59,8 @@ server.registerPath("localhost/allFiles", new ExampleHandler());
|
|||
// We will provide some convenient handlers for static, CGI, and reverse proxy
|
||||
|
||||
//if the passed file is a single file, it's a file. If it's a directory, all sub-files are auto-included
|
||||
server.registerPath("localhost/static/about.gmi", new StaticHandler("/tmp/content/about.gmi" /*, {options}*/));
|
||||
server.registerPath("localhost/static", new StaticHandler("/tmp/content/" /*, {options}*/));
|
||||
server.registerPath("localhost/static/about.gmi", new StaticHandler("README.gmi" /*, {options}*/));
|
||||
server.registerPath("localhost/static", new StaticHandler("static/" /*, {options}*/));
|
||||
// server.registerPath("localhost/static", new StaticHandler("/tmp/yourmom/" /*, {options}*/));
|
||||
|
||||
//The file passed to CGI handler must exist and be executable at run time
|
||||
|
|
|
@ -81,8 +81,10 @@ export default class ReverseProxyHandler extends DefaultHandler {
|
|||
|
||||
console.log(`Attempting to proxy ${toServe}.`);
|
||||
try {
|
||||
await geminiReq(toServe, socket)
|
||||
return "";
|
||||
// Add back url.search
|
||||
await geminiReq(toServe + url.search, socket);
|
||||
// geminiReq has already handled writing data back to the stream
|
||||
return false;
|
||||
}catch (err) {
|
||||
console.log("Something went wrong with the proxy.");
|
||||
console.error(err);
|
||||
|
|
|
@ -3,6 +3,9 @@ import * as fs from "fs";
|
|||
|
||||
import DefaultHandler from "./default.js";
|
||||
|
||||
let fileTypeFromFile;
|
||||
import("file-type").then(ft => fileTypeFromFile = ft.default.fromFile).catch(console.error);
|
||||
|
||||
export default class StaticHandler extends DefaultHandler {
|
||||
constructor(basePath, directoryList = false) {
|
||||
super();
|
||||
|
@ -18,7 +21,7 @@ export default class StaticHandler extends DefaultHandler {
|
|||
|
||||
// This is the handler
|
||||
// url is the URL object, with url.pathname being the path. p is the 'client-side' basepath
|
||||
handle (url, p) {
|
||||
async handle (url, p, socket) {
|
||||
const relativePath = path.relative(p, url.pathname);
|
||||
//Concat and normalize the passed URL as being relative to the base path
|
||||
const toServe = path.join(this.basePath, relativePath);
|
||||
|
@ -32,7 +35,7 @@ export default class StaticHandler extends DefaultHandler {
|
|||
if (fs.statSync(toServe).isDirectory()) {
|
||||
console.log(`File is a directory and directoryList is ${this.directoryList}.`);
|
||||
if (this.directoryList) {
|
||||
|
||||
//TODO: Static content directory listing
|
||||
} else {
|
||||
return "51 Not Found\r\n";
|
||||
}
|
||||
|
@ -43,9 +46,31 @@ export default class StaticHandler extends DefaultHandler {
|
|||
return "51 Not Found\r\n";
|
||||
}
|
||||
|
||||
//TODO: import mmmagic
|
||||
let mimeType = "text/gemini";
|
||||
|
||||
if (fileTypeFromFile) {
|
||||
const type = await fileTypeFromFile(toServe);
|
||||
// `type` will be undefined if the file is plaintext
|
||||
// In which case we want to fall down to serving plaintext
|
||||
if (type) {
|
||||
mimeType = type.mime;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: some file-ending based logic, please
|
||||
//So we can serve md and txt
|
||||
|
||||
|
||||
//TODO: convert line endings
|
||||
const data = fs.readFileSync(toServe);
|
||||
return "20 text/gemini\r\n" + data;
|
||||
// I read somewhere that `readFileSync` on every request is a bad idea, but I don't care
|
||||
socket.write(`20 ${mimeType}\r\n`);
|
||||
const fileReader = fs.createReadStream(toServe);
|
||||
//This function (handle) needs to return after we're done writing the file to the stream
|
||||
//Because the place that called this is going to handle .end() the socket
|
||||
//So we don't end the socket here, and we make a promise to wait for the reader to end before returning
|
||||
//It would be great if we could just not return from this function but I don't think that's how this works
|
||||
fileReader.pipe(socket, {end: false});
|
||||
await new Promise((res, rej) => fileReader.on("end", res));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
{
|
||||
"name": "astronomical-theater",
|
||||
"version": "v2.0.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "astronomical-theater",
|
||||
"version": "v2.0.5",
|
||||
"license": "CC0-1.0",
|
||||
"optionalDependencies": {
|
||||
"file-type": "^16.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "16.5.3",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.3.tgz",
|
||||
"integrity": "sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"readable-web-to-node-stream": "^3.0.0",
|
||||
"strtok3": "^6.2.4",
|
||||
"token-types": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/peek-readable": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.1.tgz",
|
||||
"integrity": "sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-web-to-node-stream": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
|
||||
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.2.4.tgz",
|
||||
"integrity": "sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"peek-readable": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.1.1.tgz",
|
||||
"integrity": "sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"optional": true
|
||||
},
|
||||
"file-type": {
|
||||
"version": "16.5.3",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.3.tgz",
|
||||
"integrity": "sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"readable-web-to-node-stream": "^3.0.0",
|
||||
"strtok3": "^6.2.4",
|
||||
"token-types": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"optional": true
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"optional": true
|
||||
},
|
||||
"peek-readable": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.1.tgz",
|
||||
"integrity": "sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==",
|
||||
"optional": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"readable-web-to-node-stream": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
|
||||
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"readable-stream": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"optional": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"strtok3": {
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.2.4.tgz",
|
||||
"integrity": "sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"peek-readable": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"token-types": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.1.1.tgz",
|
||||
"integrity": "sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
{
|
||||
"name": "astronomical-theater",
|
||||
"version": "v2.0.5",
|
||||
"version": "v2.1.0",
|
||||
"author": "Matthias",
|
||||
"license": "CC0-1.0",
|
||||
"license": "UNLICENSE",
|
||||
"type": "module",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "node main.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"file-type": "^16.5.3"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Loading…
Reference in New Issue