Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: upgrade to Node.js 20 #4

Merged
merged 4 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16
20
1 change: 1 addition & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("eslint-plugin-matrix-org/.prettierrc.js");
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:16-alpine
FROM node:20-alpine

COPY package.json yarn.lock ./
RUN yarn install --production
Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"eslint": "^7.24.0",
"eslint": "^8.54.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-matrix-org": "^1.2.1",
"eslint-plugin-unicorn": "^50.0.1",
"husky": "^7.0.4",
"nodemon": "^2.0.18",
"ts-node": "^10.8.1",
"prettier": "^3.1.1",
"semantic-release": "^19.0.2",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
}
}
30 changes: 15 additions & 15 deletions release.config.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
module.exports = {
branches: ['main'],
branches: ["main"],
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/changelog',
['@eclass/semantic-release-docker', {
'baseImageName': 'node-http-rendezvous-server',
'registries': [{
'url': 'ghcr.io',
'imageName': 'ghcr.io/matrix-org/node-http-rendezvous-server',
'user': 'GITHUB_USER',
'password': 'GITHUB_TOKEN',
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
["@eclass/semantic-release-docker", {
"baseImageName": "node-http-rendezvous-server",
"registries": [{
"url": "ghcr.io",
"imageName": "ghcr.io/matrix-org/node-http-rendezvous-server",
"user": "GITHUB_USER",
"password": "GITHUB_TOKEN",
}],
}],
['@semantic-release/git', {
assets: ['package.json', 'CHANGELOG.md'],
message: 'chore(release): ${nextRelease.version}\n\n${nextRelease.notes}',
["@semantic-release/git", {
assets: ["package.json", "CHANGELOG.md"],
message: "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}",
}],
'@semantic-release/github',
"@semantic-release/github",
],
};
6 changes: 3 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

export const ttlSeconds = parseInt(process.env.RZ_TIMEOUT ?? '60');
export const maxBytes = parseInt(process.env.RZ_MAX_BYTES ?? '10240');
export const port = parseInt(process.env.RZ_PORT ?? '8080');
export const ttlSeconds = parseInt(process.env.RZ_TIMEOUT ?? "60");
export const maxBytes = parseInt(process.env.RZ_MAX_BYTES ?? "10240");
export const port = parseInt(process.env.RZ_PORT ?? "8080");
62 changes: 31 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import express from 'express';
import morgan from 'morgan';
import { v4 } from 'uuid';
import NodeCache from 'node-cache';
import bodyParser from 'body-parser';
import cors from 'cors';
import http from 'http';
import https from 'https';
import { readFileSync } from 'fs';

import { Rendezvous } from './rendezvous';
import { maxBytes, ttlSeconds, port } from './config';
import express from "express";
import morgan from "morgan";
import { v4 } from "uuid";
import NodeCache from "node-cache";
import bodyParser from "body-parser";
import cors from "cors";
import http from "http";
import https from "https";
import { readFileSync } from "fs";

import { Rendezvous } from "./rendezvous";
import { maxBytes, ttlSeconds, port } from "./config";

const app = express();
app.use(cors({
allowedHeaders: ['Content-Type', 'If-Match', 'If-None-Match'],
exposedHeaders: ['ETag', 'Location', 'X-Max-Bytes'],
allowedHeaders: ["Content-Type", "If-Match", "If-None-Match"],
exposedHeaders: ["ETag", "Location", "X-Max-Bytes"],
}));
app.use(morgan('common'));
app.set('env', 'production');
app.set('x-powered-by', false);
app.set('etag', false);
app.use(morgan("common"));
app.set("env", "production");
app.set("x-powered-by", false);
app.set("etag", false);

// treat everything as raw
app.use(bodyParser.raw({
type: '*/*',
type: "*/*",
inflate: true,
limit: maxBytes,
}));

app.get('/health', (req, res) => res.status(200).send('OK'));
app.get("/health", (req, res) => res.status(200).send("OK"));

const rvs = new NodeCache({
checkperiod: 60,
useClones: false,
});

app.post('/', (req, res) => {
app.post("/", (req, res) => {
let id: string | undefined;
while (!id || id in rvs) {
id = v4();
Expand All @@ -61,13 +61,13 @@ app.post('/', (req, res) => {

rv.setHeaders(res);

res.setHeader('Location', id);
res.setHeader('X-Max-Bytes', maxBytes);
res.setHeader("Location", id);
res.setHeader("X-Max-Bytes", maxBytes);

return res.sendStatus(201);
});

app.put('/:id', (req, res) => {
app.put("/:id", (req, res) => {
const { id } = req.params;
const rv = rvs.get<Rendezvous>(id);

Expand All @@ -80,7 +80,7 @@ app.put('/:id', (req, res) => {
return res.sendStatus(404);
}

const ifMatch = req.headers['if-match'];
const ifMatch = req.headers["if-match"];

if (ifMatch && ifMatch !== rv.etag) {
rv.setHeaders(res);
Expand All @@ -93,7 +93,7 @@ app.put('/:id', (req, res) => {
return res.sendStatus(202);
});

app.get('/:id', (req, res) => {
app.get("/:id", (req, res) => {
const { id } = req.params;
const rv = rvs.get<Rendezvous>(id);

Expand All @@ -108,14 +108,14 @@ app.get('/:id', (req, res) => {

rv.setHeaders(res);

if (req.headers['if-none-match'] === rv.etag) {
if (req.headers["if-none-match"] === rv.etag) {
return res.sendStatus(304);
}

return rv.sendData(res);
});

app.delete('/:id', (req, res) => {
app.delete("/:id", (req, res) => {
const { id } = req.params;
if (!rvs.has(id)) {
return res.sendStatus(404);
Expand All @@ -124,10 +124,10 @@ app.delete('/:id', (req, res) => {
return res.sendStatus(204);
});

if (process.env.DEV_SSL === 'yes') {
if (process.env.DEV_SSL === "yes") {
const httpsServer = https.createServer({
key: readFileSync('./devssl/key.pem'),
cert: readFileSync('./devssl/cert.pem'),
key: readFileSync("./devssl/key.pem"),
cert: readFileSync("./devssl/cert.pem"),
}, app);
httpsServer.listen(port, () => {
console.log(`Starting rendezvous server at https://0.0.0.0:${port} with self-signed certificate`);
Expand Down
34 changes: 17 additions & 17 deletions src/rendezvous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import express from 'express';
import { createHash } from 'crypto';
import express from "express";
import { createHash } from "crypto";

export class Rendezvous {
private readonly id: string;
private readonly expiresAt: Date;
readonly ttlSeconds: number;
public readonly ttlSeconds: number;
private readonly maxBytes: number;
private data?: Buffer;
private contentType!: string;
private lastModified!: Date;
etag!: string;
public etag!: string;

constructor(id: string, ttlSeconds: number, maxBytes: number, initialRequest: express.Request) {
public constructor(id: string, ttlSeconds: number, maxBytes: number, initialRequest: express.Request) {
const now = new Date();
this.id = id;
this.ttlSeconds = ttlSeconds;
Expand All @@ -36,38 +36,38 @@ export class Rendezvous {
this.update(initialRequest);
}

update(req: express.Request) {
public update(req: express.Request): void {
this.data = req.body;
this.contentType = req.get('Content-Type') ?? 'application/octet-stream';
this.contentType = req.get("Content-Type") ?? "application/octet-stream";
this.lastModified = new Date();
const hash = createHash('sha256');
const hash = createHash("sha256");
// include ID so that it is rendezvous-specific
hash.update(this.id);
// we include last modified so that if both sides post identical data then they will hopefully still hash differently:
hash.update(this.lastModified.toISOString());
if (Buffer.isBuffer(this.data)) {
hash.update(this.data);
}
this.etag = hash.digest('base64');
this.etag = hash.digest("base64");
}

sendData(res: express.Response): void {
public sendData(res: express.Response): void {
res.status(200);
res.setHeader('Content-Type', this.contentType);
res.setHeader("Content-Type", this.contentType);
if (Buffer.isBuffer(this.data)) {
res.send(this.data);
} else {
res.send('');
res.send("");
}
}

expired(): boolean {
public expired(): boolean {
return this.expiresAt < new Date();
}

setHeaders(res: express.Response): void {
res.setHeader('ETag', this.etag);
res.setHeader('Expires', this.expiresAt.toUTCString());
res.setHeader('Last-Modified', this.lastModified.toUTCString());
public setHeaders(res: express.Response): void {
res.setHeader("ETag", this.etag);
res.setHeader("Expires", this.expiresAt.toUTCString());
res.setHeader("Last-Modified", this.lastModified.toUTCString());
}
}
Loading
Loading