-
Notifications
You must be signed in to change notification settings - Fork 141
/
Copy pathindex.ts
125 lines (102 loc) · 3.02 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
117
118
119
120
121
122
123
124
125
import validator from "validator";
import {
fetch as undiciFetch,
EventSource,
EnvHttpProxyAgent,
type ErrorEvent,
type MessageEvent,
} from "undici";
import url from "node:url";
import querystring from "node:querystring";
type Severity = "info" | "error";
interface Options {
source: string;
target: string;
logger?: Pick<Console, Severity>;
fetch?: any;
}
const proxyAgent = new EnvHttpProxyAgent();
class Client {
#source: string;
#target: string;
#fetch: typeof undiciFetch;
#logger: Pick<Console, Severity>;
#events!: EventSource;
constructor({
source,
target,
logger = console,
fetch = undiciFetch,
}: 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 = undiciFetch } = {}) {
const response = await fetch("https://smee.io/new", {
method: "HEAD",
redirect: "manual",
dispatcher: proxyAgent,
});
const address = response.headers.get("location");
if (!address) {
throw new Error("Failed to create channel");
}
return address;
}
async onmessage(msg: MessageEvent<string>) {
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"],
body,
headers,
dispatcher: proxyAgent,
});
this.#logger.info(`POST ${response.url} - ${response.status}`);
} catch (err) {
this.#logger.error(err);
}
}
onopen() {
this.#logger.info("Connected", this.#events.url);
}
onerror(err: ErrorEvent) {
this.#logger.error(err);
}
start() {
const events = new EventSource(this.#source, {
dispatcher: proxyAgent,
});
// 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 default Client;