-
Notifications
You must be signed in to change notification settings - Fork 138
/
index.ts
116 lines (94 loc) · 2.83 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import validator from "validator";
import EventSource from "eventsource";
import url from "url";
import querystring from "querystring";
type Severity = "info" | "error";
interface Options {
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,
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.");
}
}
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;
}
async onmessage(msg: any) {
const data = JSON.parse(msg.data);
const target = url.parse(this.target, true);
const mergedQuery = { ...target.query, ...data.query };
target.search = querystring.stringify(mergedQuery);
delete data.query;
const body = JSON.stringify(data.body);
delete data.body;
const headers: { [key: string]: any } = {};
Object.keys(data).forEach((key) => {
headers[key] = data[key];
});
// Don't forward the host header. As it causes issues with some servers
// See https://github.com/probot/smee-client/issues/295
// See https://github.com/probot/smee-client/issues/187
delete headers["host"];
headers["content-length"] = Buffer.byteLength(body);
headers["content-type"] = "application/json";
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);
}
onerror(err: any) {
this.logger.error(err);
}
start() {
const events = new EventSource(this.source, {
proxy: process.env.HTTP_PROXY || process.env.HTTPS_PROXY,
});
// Reconnect immediately
(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));
this.logger.info(`Forwarding ${this.source} to ${this.target}`);
this.events = events;
return events;
}
}
export = Client;