Skip to content

Commit

Permalink
refactor: modernize code and remove dependencies (#260)
Browse files Browse the repository at this point in the history
This PR refactors the whole codebase. Removes unecessary deps, use of
built in fetch.

Should be released as semver-major.

I was first confused, because it seemed, that it was bugged, because the
payload of the endpoint was not returned. Then I tested it with the
original smee-client and realized that it is a one way thing... smee.io
does not return the answer of the webserver. Please correct me if I am
wrong.

Coverage is improved significantly.

```sh
 % Coverage report from v8
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   94.49 |    88.88 |   66.66 |   94.49 |                   
 index.ts |   94.49 |    88.88 |   66.66 |   94.49 | 80-81,85-86,89-90 
----------|---------|----------|---------|---------|-------------------
```

BREAKING CHANGE: drop support for Node <18

BREAKING CHANGE: use native `fetch` for requests
  • Loading branch information
Uzlopak authored Nov 17, 2023
1 parent e073d96 commit da656a4
Show file tree
Hide file tree
Showing 9 changed files with 2,036 additions and 7,796 deletions.
117 changes: 117 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: CI
on:
workflow_dispatch:
push:
branches:
- master
pull_request:
types:
- opened
- synchronize

permissions:
contents: read

jobs:
dependency-review:
name: Dependency Review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Dependency review
uses: actions/dependency-review-action@v3

license-check:
name: Check Licenses
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Check Licenses
run: npx license-checker --production --summary --onlyAllow="0BSD;Apache-2.0;Apache 2.0;Python-2.0;BSD-2-Clause;BSD-3-Clause;ISC;MIT"

codeql:
name: CodeQL
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: javascript-typescript
queries: security-and-quality

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:javascript-typescript"

lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint

test-unit:
name: Test on Node ${{ matrix.node-version }} and ${{ matrix.os }}
strategy:
matrix:
node-version:
- 18
- 20
- 21
os:
- ubuntu-latest
- macos-latest
- windows-latest
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Test
run: npm run test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ server/public/main.min.*
coverage
.vscode
index.js
index.js.map
*.d.ts
*.tsbuildinfo
21 changes: 0 additions & 21 deletions .travis.yml

This file was deleted.

13 changes: 6 additions & 7 deletions bin/smee.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ program
.option('-P, --path <path>', 'URL path to post proxied requests to`', '/')
.parse(process.argv)

let target
if (program.target) {
target = program.target
} else {
target = `http://127.0.0.1:${program.port}${program.path}`
}
const opts = program.opts()

const {
target = `http://127.0.0.1:${opts.port}${opts.path}`
} = opts

async function setup () {
let source = program.url
let source = opts.url

if (!source) {
source = await Client.createChannel()
Expand Down
119 changes: 70 additions & 49 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,109 @@
import validator from 'validator'
import EventSource from 'eventsource'
import superagent from 'superagent'
import url from 'url'
import querystring from 'querystring'
import validator from "validator";
import EventSource from "eventsource";
import url from "url";
import querystring from "querystring";

type Severity = 'info' | 'error'
type Severity = "info" | "error";

interface Options {
source: string
target: string
logger?: Pick<Console, Severity>
source: string;
target: string;
logger?: Pick<Console, Severity>;
fetch?: any;
}

class Client {
source: string;
target: string;
fetch: typeof global.fetch;
logger: Pick<Console, Severity>;
events!: EventSource;

constructor ({ source, target, logger = console }: Options) {
this.source = source
this.target = target
this.logger = logger!
constructor({
source,
target,
logger = console,
fetch = global.fetch,
}: Options) {
this.source = source;
this.target = target;
this.logger = logger!;
this.fetch = fetch;

if (!validator.isURL(this.source)) {
throw new Error('The provided URL is invalid.')
throw new Error("The provided URL is invalid.");
}
}

static async createChannel () {
return superagent.head('https://smee.io/new').redirects(0).catch((err) => {
return err.response.headers.location
})
static async createChannel({ fetch = global.fetch } = {}) {
const response = await fetch("https://smee.io/new", {
method: "HEAD",
redirect: "manual",
});
const address = response.headers.get("location");
if (!address) {
throw new Error("Failed to create channel");
}
return address;
}

onmessage (msg: any) {
const data = JSON.parse(msg.data)
async onmessage(msg: any) {
const data = JSON.parse(msg.data);

const target = url.parse(this.target, true)
const mergedQuery = Object.assign(target.query, data.query)
target.search = querystring.stringify(mergedQuery)
const target = url.parse(this.target, true);
const mergedQuery = { ...target.query, ...data.query };
target.search = querystring.stringify(mergedQuery);

delete data.query
delete data.query;

const req = superagent.post(url.format(target)).send(data.body)
const body = JSON.stringify(data.body);
delete data.body;

delete data.body
const headers: { [key: string]: any } = {};

Object.keys(data).forEach(key => {
req.set(key, data[key])
})
Object.keys(data).forEach((key) => {
headers[key] = data[key];
});

req.end((err, res) => {
if (err) {
this.logger.error(err)
} else {
this.logger.info(`${req.method} ${req.url} - ${res.status}`)
}
})
headers["content-length"] = Buffer.byteLength(body);

try {
const response = await this.fetch(url.format(target), {
method: "POST",
mode: data["sec-fetch-mode"],
cache: "default",
body,
headers,
});
this.logger.info(`POST ${response.url} - ${response.status}`);
} catch (err) {
this.logger.error(err);
}
}

onopen () {
this.logger.info('Connected', this.events.url)
onopen() {
this.logger.info("Connected", this.events.url);
}

onerror (err: any) {
this.logger.error(err)
onerror(err: any) {
this.logger.error(err);
}

start () {
start() {
const events = new EventSource(this.source);

// Reconnect immediately
(events as any).reconnectInterval = 0 // This isn't a valid property of EventSource
(events as any).reconnectInterval = 0; // This isn't a valid property of EventSource

events.addEventListener('message', this.onmessage.bind(this))
events.addEventListener('open', this.onopen.bind(this))
events.addEventListener('error', this.onerror.bind(this))
events.addEventListener("message", this.onmessage.bind(this));
events.addEventListener("open", this.onopen.bind(this));
events.addEventListener("error", this.onerror.bind(this));

this.logger.info(`Forwarding ${this.source} to ${this.target}`)
this.events = events
this.logger.info(`Forwarding ${this.source} to ${this.target}`);
this.events = events;

return events
return events;
}
}

export = Client
export = Client;
Loading

0 comments on commit da656a4

Please sign in to comment.