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

refactor: modernize code and remove dependencies #260

Merged
merged 14 commits into from
Nov 17, 2023
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