@hahnfeld/msrelay-provider 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -37
- package/dist/plugin.js +145 -62
- package/dist/provider.js +11 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,48 +45,24 @@ This plugin provides a tunnel — it makes a local port reachable via a stable H
|
|
|
45
45
|
openacp plugin install @hahnfeld/msrelay-provider
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
The install wizard
|
|
48
|
+
The install wizard walks you through the full Azure setup — showing the `az` CLI commands for each step and prompting for the values. You'll need the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) installed.
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
2. Hybrid Connection name (e.g., `bot-endpoint`)
|
|
52
|
-
3. SAS policy name and key (with `Listen` permission)
|
|
53
|
-
4. Local port to forward to (3978 for Bot Framework, 21420 for OpenACP API server, or any custom port)
|
|
50
|
+
The wizard covers:
|
|
54
51
|
|
|
55
|
-
|
|
52
|
+
1. Creating a Relay namespace
|
|
53
|
+
2. Creating a Hybrid Connection (with client authorization disabled)
|
|
54
|
+
3. Creating a listen-only SAS policy
|
|
55
|
+
4. Retrieving the SAS key
|
|
56
|
+
5. Choosing a local port to forward to
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
On reinstall, existing values are pre-filled so you can press Enter to keep them. The SAS key is masked and can be kept without re-entering.
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
# 1. Create Relay namespace
|
|
61
|
-
az relay namespace create \
|
|
62
|
-
--resource-group <rg> --name <namespace> --location westus2
|
|
63
|
-
|
|
64
|
-
# 2. Create Hybrid Connection
|
|
65
|
-
az relay hyco create \
|
|
66
|
-
--resource-group <rg> --namespace-name <namespace> --name my-connection
|
|
67
|
-
|
|
68
|
-
# 3. Disable client authorization (required if callers can't send SAS headers —
|
|
69
|
-
# e.g., Bot Framework, external webhooks, third-party services)
|
|
70
|
-
az relay hyco update \
|
|
71
|
-
--resource-group <rg> --namespace-name <namespace> \
|
|
72
|
-
--name my-connection --requires-client-authorization false
|
|
73
|
-
|
|
74
|
-
# 4. Create listen-only SAS policy (least privilege)
|
|
75
|
-
az relay hyco authorization-rule create \
|
|
76
|
-
--resource-group <rg> --namespace-name <namespace> \
|
|
77
|
-
--hybrid-connection-name my-connection \
|
|
78
|
-
--name ListenOnly --rights Listen
|
|
79
|
-
|
|
80
|
-
# 5. Retrieve the key
|
|
81
|
-
az relay hyco authorization-rule keys list \
|
|
82
|
-
--resource-group <rg> --namespace-name <namespace> \
|
|
83
|
-
--hybrid-connection-name my-connection --name ListenOnly
|
|
84
|
-
```
|
|
60
|
+
After setup, the wizard tests connectivity by opening a real WebSocket to Azure Relay.
|
|
85
61
|
|
|
86
|
-
The resulting endpoint URL is:
|
|
62
|
+
The resulting public endpoint URL is:
|
|
87
63
|
|
|
88
64
|
```
|
|
89
|
-
https://<namespace>.servicebus.windows.net
|
|
65
|
+
https://<namespace>.servicebus.windows.net/<connection>
|
|
90
66
|
```
|
|
91
67
|
|
|
92
68
|
Point your external callers (Bot Framework messaging endpoint, webhook URLs, etc.) at this URL.
|
|
@@ -113,13 +89,13 @@ openacp plugin configure @hahnfeld/msrelay-provider
|
|
|
113
89
|
| Command | Description |
|
|
114
90
|
|---------|-------------|
|
|
115
91
|
| `/relay` | Show connection status, uptime, request/error counts |
|
|
116
|
-
| `/relay auth` |
|
|
92
|
+
| `/relay auth` | Test connectivity by connecting to Azure Relay |
|
|
117
93
|
|
|
118
94
|
## Development
|
|
119
95
|
|
|
120
96
|
```bash
|
|
121
97
|
pnpm install
|
|
122
|
-
pnpm test # Run tests (
|
|
98
|
+
pnpm test # Run tests (42 tests)
|
|
123
99
|
pnpm build # Compile TypeScript
|
|
124
100
|
pnpm dev # Watch mode
|
|
125
101
|
pnpm lint # Type-check without emitting
|
package/dist/plugin.js
CHANGED
|
@@ -10,20 +10,41 @@ function isValidNamespace(v) {
|
|
|
10
10
|
function isValidConnectionName(v) {
|
|
11
11
|
return /^[a-zA-Z0-9._-]+$/.test(v);
|
|
12
12
|
}
|
|
13
|
-
/** Test
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
/** Test connectivity by opening a real WebSocket to Azure Relay. */
|
|
14
|
+
function testRelayConnection(namespace, connectionName, keyName, keyValue, timeoutMs = 10_000) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
try {
|
|
17
|
+
const hycoHttps = require("hyco-https");
|
|
18
|
+
const uri = hycoHttps.createRelayListenUri(namespace, connectionName);
|
|
19
|
+
const server = hycoHttps.createRelayedServer({ server: uri, token: () => hycoHttps.createRelayToken(uri, keyName, keyValue) }, () => { });
|
|
20
|
+
let settled = false;
|
|
21
|
+
const settle = (result) => {
|
|
22
|
+
if (!settled) {
|
|
23
|
+
settled = true;
|
|
24
|
+
try {
|
|
25
|
+
server.close();
|
|
26
|
+
}
|
|
27
|
+
catch { /* ignore */ }
|
|
28
|
+
resolve(result);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
settle({ ok: false, error: "Connection timed out — check namespace, connection name, and network access" });
|
|
33
|
+
}, timeoutMs);
|
|
34
|
+
server.on("listening", () => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
settle({ ok: true });
|
|
37
|
+
});
|
|
38
|
+
server.on("error", (err) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
settle({ ok: false, error: `Connection failed: ${err.message}` });
|
|
41
|
+
});
|
|
42
|
+
server.listen();
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
resolve({ ok: false, error: `Setup failed: ${err.message}` });
|
|
21
46
|
}
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
return { ok: false, error: `Token generation failed: ${err.message}` };
|
|
26
|
-
}
|
|
47
|
+
});
|
|
27
48
|
}
|
|
28
49
|
/** Format elapsed time as human-readable duration. */
|
|
29
50
|
function formatUptime(startedAt) {
|
|
@@ -64,7 +85,7 @@ export function createRelayPlugin() {
|
|
|
64
85
|
let provider = null;
|
|
65
86
|
return {
|
|
66
87
|
name: "@hahnfeld/msrelay-provider",
|
|
67
|
-
version: "0.1.
|
|
88
|
+
version: "0.1.4",
|
|
68
89
|
description: "Azure Relay Hybrid Connections tunnel provider — private HTTP tunneling via Azure backbone",
|
|
69
90
|
essential: false,
|
|
70
91
|
permissions: [
|
|
@@ -77,30 +98,54 @@ export function createRelayPlugin() {
|
|
|
77
98
|
// ─── Interactive Install Wizard ──────────────────────────────────────
|
|
78
99
|
async install(ctx) {
|
|
79
100
|
const { terminal, settings } = ctx;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
" 2. Hybrid Connection with client authorization DISABLED\n" +
|
|
86
|
-
" 3. SAS key with Listen permission\n" +
|
|
101
|
+
// Load existing config for pre-filling on reinstall
|
|
102
|
+
const existing = await settings.getAll();
|
|
103
|
+
terminal.note("This wizard walks you through setting up Azure Relay Hybrid Connections.\n" +
|
|
104
|
+
"It exposes local ports through Azure's private backbone — no public\n" +
|
|
105
|
+
"internet exposure, no inbound ports.\n" +
|
|
87
106
|
"\n" +
|
|
88
|
-
"
|
|
107
|
+
"You'll need the Azure CLI installed (az). Each step shows the command\n" +
|
|
108
|
+
"to run. If you've already provisioned these resources, just enter the\n" +
|
|
109
|
+
"existing values when prompted.", "Azure Relay Setup");
|
|
89
110
|
// ── Step 1: Relay Namespace ──
|
|
90
|
-
|
|
91
|
-
|
|
111
|
+
terminal.note("Create a Relay namespace (if you don't have one):\n" +
|
|
112
|
+
"\n" +
|
|
113
|
+
" az relay namespace create \\\n" +
|
|
114
|
+
" --resource-group <resource-group> \\\n" +
|
|
115
|
+
" --name <choose-a-name> \\\n" +
|
|
116
|
+
" --location westus2\n" +
|
|
117
|
+
"\n" +
|
|
118
|
+
"The namespace becomes <name>.servicebus.windows.net", "Step 1: Create Relay Namespace");
|
|
119
|
+
const namespaceInput = await terminal.text({
|
|
120
|
+
message: "Azure Relay namespace (e.g., myrelay or myrelay.servicebus.windows.net):",
|
|
121
|
+
defaultValue: existing.relayNamespace ?? undefined,
|
|
92
122
|
validate: (v) => {
|
|
93
|
-
|
|
94
|
-
if (!trimmed)
|
|
123
|
+
if (!v.trim())
|
|
95
124
|
return "Namespace is required";
|
|
96
|
-
if (!isValidNamespace(trimmed))
|
|
97
|
-
return "Must be <name>.servicebus.windows.net";
|
|
98
125
|
return undefined;
|
|
99
126
|
},
|
|
100
127
|
});
|
|
101
|
-
|
|
128
|
+
const ns = namespaceInput.trim().endsWith(".servicebus.windows.net")
|
|
129
|
+
? namespaceInput.trim()
|
|
130
|
+
: `${namespaceInput.trim()}.servicebus.windows.net`;
|
|
131
|
+
if (!isValidNamespace(ns)) {
|
|
132
|
+
terminal.log.warning(`"${ns}" doesn't look like a valid namespace — continuing anyway`);
|
|
133
|
+
}
|
|
134
|
+
const nsName = ns.replace(".servicebus.windows.net", "");
|
|
135
|
+
// ── Step 2: Hybrid Connection ──
|
|
136
|
+
terminal.note("Create a Hybrid Connection with client authorization disabled:\n" +
|
|
137
|
+
"\n" +
|
|
138
|
+
` az relay hyco create \\\n` +
|
|
139
|
+
` --resource-group <resource-group> \\\n` +
|
|
140
|
+
` --namespace-name ${nsName} \\\n` +
|
|
141
|
+
` --name <choose-a-name> \\\n` +
|
|
142
|
+
` --requires-client-authorization false\n` +
|
|
143
|
+
"\n" +
|
|
144
|
+
"IMPORTANT: --requires-client-authorization cannot be changed after\n" +
|
|
145
|
+
"creation. If you need to change it, delete and recreate the connection.", "Step 2: Create Hybrid Connection");
|
|
102
146
|
const connectionName = await terminal.text({
|
|
103
147
|
message: "Hybrid Connection name (e.g., bot-endpoint):",
|
|
148
|
+
defaultValue: existing.hybridConnectionName ?? undefined,
|
|
104
149
|
validate: (v) => {
|
|
105
150
|
const trimmed = v.trim();
|
|
106
151
|
if (!trimmed)
|
|
@@ -110,38 +155,72 @@ export function createRelayPlugin() {
|
|
|
110
155
|
return undefined;
|
|
111
156
|
},
|
|
112
157
|
});
|
|
113
|
-
|
|
158
|
+
const hc = connectionName.trim();
|
|
159
|
+
// ── Step 3: SAS Policy ──
|
|
160
|
+
terminal.note("Create a listen-only SAS authorization rule:\n" +
|
|
161
|
+
"\n" +
|
|
162
|
+
` az relay hyco authorization-rule create \\\n` +
|
|
163
|
+
` --resource-group <resource-group> \\\n` +
|
|
164
|
+
` --namespace-name ${nsName} \\\n` +
|
|
165
|
+
` --hybrid-connection-name ${hc} \\\n` +
|
|
166
|
+
` --name ListenOnly --rights Listen\n` +
|
|
167
|
+
"\n" +
|
|
168
|
+
"Use a listen-only policy — this plugin only needs Listen permission.\n" +
|
|
169
|
+
"Do not use RootManageSharedAccessKey.", "Step 3: Create SAS Policy");
|
|
114
170
|
const keyName = await terminal.text({
|
|
115
171
|
message: "SAS policy name:",
|
|
116
|
-
defaultValue: "ListenOnly",
|
|
172
|
+
defaultValue: existing.sasKeyName ?? "ListenOnly",
|
|
117
173
|
validate: (v) => {
|
|
118
174
|
if (!v.trim())
|
|
119
|
-
return "
|
|
175
|
+
return "Policy name is required";
|
|
120
176
|
return undefined;
|
|
121
177
|
},
|
|
122
178
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
179
|
+
const sasName = keyName.trim();
|
|
180
|
+
// ── Step 4: SAS Key ──
|
|
181
|
+
terminal.note("Get the SAS key with this command:\n" +
|
|
182
|
+
"\n" +
|
|
183
|
+
` az relay hyco authorization-rule keys list \\\n` +
|
|
184
|
+
` --resource-group <resource-group> \\\n` +
|
|
185
|
+
` --namespace-name ${nsName} \\\n` +
|
|
186
|
+
` --hybrid-connection-name ${hc} \\\n` +
|
|
187
|
+
` --name ${sasName} \\\n` +
|
|
188
|
+
` --query primaryKey -o tsv\n` +
|
|
189
|
+
"\n" +
|
|
190
|
+
"Copy the output — that's your SAS key value.", "Step 4: Get SAS Key");
|
|
191
|
+
const hasExistingKey = !!(existing.sasKeyValue);
|
|
192
|
+
let keyValue = await terminal.password({
|
|
193
|
+
message: hasExistingKey
|
|
194
|
+
? "SAS key value — press Enter to keep current:"
|
|
195
|
+
: "SAS key value (paste the primaryKey):",
|
|
126
196
|
validate: (v) => {
|
|
127
|
-
if (!v.trim())
|
|
197
|
+
if (!(v ?? "").trim() && !hasExistingKey)
|
|
128
198
|
return "Key value is required";
|
|
129
199
|
return undefined;
|
|
130
200
|
},
|
|
131
201
|
});
|
|
132
|
-
|
|
133
|
-
|
|
202
|
+
if (!(keyValue ?? "").trim() && hasExistingKey) {
|
|
203
|
+
keyValue = existing.sasKeyValue;
|
|
204
|
+
}
|
|
205
|
+
// ── Step 6: Port ──
|
|
134
206
|
terminal.note("This tunnel can point to any local port. Common choices:\n" +
|
|
135
207
|
"\n" +
|
|
136
208
|
" 3978 — Teams adapter (Microsoft Bot Framework default)\n" +
|
|
137
209
|
" 21420 — OpenACP API server\n" +
|
|
138
210
|
"\n" +
|
|
139
|
-
"You can change this later with
|
|
211
|
+
"You can change this later with:\n" +
|
|
212
|
+
" openacp plugin configure @hahnfeld/msrelay-provider", "Step 5: Port Selection");
|
|
213
|
+
const existingPort = existing.port;
|
|
140
214
|
const envPort = process.env.PORT ? Number(process.env.PORT) : null;
|
|
141
215
|
const portChoice = await terminal.select({
|
|
142
216
|
message: "Which port should the tunnel expose?",
|
|
143
217
|
options: [
|
|
144
|
-
...(
|
|
218
|
+
...(existingPort && existingPort !== 3978 && existingPort !== 21420
|
|
219
|
+
? [{ value: String(existingPort), label: `${existingPort} (current config)`, hint: "Previously configured" }]
|
|
220
|
+
: []),
|
|
221
|
+
...(envPort && envPort !== existingPort
|
|
222
|
+
? [{ value: String(envPort), label: `${envPort} (from PORT env var)`, hint: "Current environment" }]
|
|
223
|
+
: []),
|
|
145
224
|
{ value: "3978", label: "3978", hint: "Teams adapter (Bot Framework default)" },
|
|
146
225
|
{ value: "21420", label: "21420", hint: "OpenACP API server" },
|
|
147
226
|
{ value: "custom", label: "Other port" },
|
|
@@ -151,6 +230,7 @@ export function createRelayPlugin() {
|
|
|
151
230
|
if (portChoice === "custom") {
|
|
152
231
|
const portStr = await terminal.text({
|
|
153
232
|
message: "Local port to expose:",
|
|
233
|
+
defaultValue: existingPort ? String(existingPort) : undefined,
|
|
154
234
|
validate: (v) => {
|
|
155
235
|
const trimmed = v.trim();
|
|
156
236
|
if (!trimmed)
|
|
@@ -166,39 +246,42 @@ export function createRelayPlugin() {
|
|
|
166
246
|
else {
|
|
167
247
|
port = Number(portChoice);
|
|
168
248
|
}
|
|
169
|
-
// ── Step
|
|
249
|
+
// ── Step 7: Connectivity Test (optional) ──
|
|
170
250
|
const wantTest = await terminal.confirm({
|
|
171
|
-
message: "Test connectivity now? (
|
|
172
|
-
initialValue:
|
|
251
|
+
message: "Test connectivity now? (connects to Azure Relay for up to 10s)",
|
|
252
|
+
initialValue: true,
|
|
173
253
|
});
|
|
174
254
|
if (wantTest) {
|
|
175
255
|
const spin = terminal.spinner();
|
|
176
|
-
spin.start("
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
spin.stop("
|
|
256
|
+
spin.start("Connecting to Azure Relay...");
|
|
257
|
+
const result = await testRelayConnection(ns, hc, sasName, keyValue.trim());
|
|
258
|
+
if (result.ok) {
|
|
259
|
+
spin.stop("Connected to Azure Relay successfully");
|
|
180
260
|
}
|
|
181
261
|
else {
|
|
182
|
-
spin.fail(
|
|
262
|
+
spin.fail(result.error);
|
|
183
263
|
terminal.log.warning("Installation will continue — you can test connectivity later with /relay auth");
|
|
184
264
|
}
|
|
185
265
|
}
|
|
186
|
-
// ── Step
|
|
266
|
+
// ── Step 8: Save ──
|
|
187
267
|
await settings.setAll({
|
|
188
268
|
enabled: true,
|
|
189
269
|
port,
|
|
190
|
-
relayNamespace:
|
|
191
|
-
hybridConnectionName:
|
|
192
|
-
sasKeyName:
|
|
270
|
+
relayNamespace: ns,
|
|
271
|
+
hybridConnectionName: hc,
|
|
272
|
+
sasKeyName: sasName,
|
|
193
273
|
sasKeyValue: keyValue.trim(),
|
|
194
274
|
});
|
|
195
275
|
terminal.log.success("Azure Relay provider configured!");
|
|
196
276
|
terminal.log.info("");
|
|
197
|
-
terminal.note(`Namespace: ${
|
|
198
|
-
`Connection: ${
|
|
199
|
-
`SAS policy: ${
|
|
277
|
+
terminal.note(`Namespace: ${ns}\n` +
|
|
278
|
+
`Connection: ${hc}\n` +
|
|
279
|
+
`SAS policy: ${sasName}\n` +
|
|
200
280
|
`Port: ${port}\n` +
|
|
201
|
-
`Public URL: https://${
|
|
281
|
+
`Public URL: https://${ns}/${hc}\n` +
|
|
282
|
+
"\n" +
|
|
283
|
+
"Point your Bot Framework messaging endpoint (or webhook URLs) at\n" +
|
|
284
|
+
"the public URL above.", "Configuration Summary");
|
|
202
285
|
},
|
|
203
286
|
// ─── Configure (post-install changes) ────────────────────────────────
|
|
204
287
|
async configure(ctx) {
|
|
@@ -270,7 +353,7 @@ export function createRelayPlugin() {
|
|
|
270
353
|
break;
|
|
271
354
|
}
|
|
272
355
|
case "sasKeyValue": {
|
|
273
|
-
const val = await terminal.
|
|
356
|
+
const val = await terminal.password({
|
|
274
357
|
message: "SAS key value:",
|
|
275
358
|
validate: (v) => {
|
|
276
359
|
if (!v.trim())
|
|
@@ -308,10 +391,10 @@ export function createRelayPlugin() {
|
|
|
308
391
|
}
|
|
309
392
|
case "auth": {
|
|
310
393
|
const spin = terminal.spinner();
|
|
311
|
-
spin.start("
|
|
312
|
-
const result =
|
|
394
|
+
spin.start("Connecting to Azure Relay...");
|
|
395
|
+
const result = await testRelayConnection(current.relayNamespace, current.hybridConnectionName, current.sasKeyName, current.sasKeyValue);
|
|
313
396
|
if (result.ok) {
|
|
314
|
-
spin.stop("
|
|
397
|
+
spin.stop("Connected to Azure Relay successfully");
|
|
315
398
|
}
|
|
316
399
|
else {
|
|
317
400
|
spin.fail(result.error);
|
|
@@ -394,9 +477,9 @@ export function createRelayPlugin() {
|
|
|
394
477
|
const raw = args.raw.trim();
|
|
395
478
|
if (raw === "auth") {
|
|
396
479
|
const cfg = config;
|
|
397
|
-
const result =
|
|
480
|
+
const result = await testRelayConnection(cfg.relayNamespace, cfg.hybridConnectionName, cfg.sasKeyName, cfg.sasKeyValue);
|
|
398
481
|
if (result.ok) {
|
|
399
|
-
return { type: "text", text: "
|
|
482
|
+
return { type: "text", text: "Azure Relay connectivity: OK" };
|
|
400
483
|
}
|
|
401
484
|
return { type: "error", message: result.error };
|
|
402
485
|
}
|
package/dist/provider.js
CHANGED
|
@@ -171,19 +171,17 @@ export class AzureRelayProvider {
|
|
|
171
171
|
onUnexpectedClose();
|
|
172
172
|
}
|
|
173
173
|
});
|
|
174
|
-
|
|
174
|
+
// hyco-https listen() takes no arguments — readiness is signaled
|
|
175
|
+
// via the 'listening' event when the control channel WebSocket opens.
|
|
176
|
+
server.on("listening", () => {
|
|
175
177
|
clearTimeout(timeout);
|
|
176
|
-
if (err) {
|
|
177
|
-
this.server = null;
|
|
178
|
-
settle(() => reject(new Error(`Azure Relay listen failed: ${err.message}`)));
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
178
|
this.publicUrl = `https://${relayNamespace}/${hybridConnectionName}`;
|
|
182
179
|
if (!this.metrics.startedAt) {
|
|
183
180
|
this.metrics.startedAt = new Date();
|
|
184
181
|
}
|
|
185
182
|
settle(() => resolve(this.publicUrl));
|
|
186
183
|
});
|
|
184
|
+
server.listen();
|
|
187
185
|
});
|
|
188
186
|
}
|
|
189
187
|
/**
|
|
@@ -236,12 +234,18 @@ export class AzureRelayProvider {
|
|
|
236
234
|
this.sendError(relayRes, 400, "Bad Request", `Invalid request URL: ${rawUrl}`);
|
|
237
235
|
return;
|
|
238
236
|
}
|
|
237
|
+
// Azure Relay prepends the Hybrid Connection name to the path.
|
|
238
|
+
// e.g., /bot-endpoint/api/messages → /api/messages
|
|
239
|
+
const prefix = `/${this.config.hybridConnectionName}`;
|
|
240
|
+
const forwardPath = rawUrl.startsWith(prefix)
|
|
241
|
+
? (rawUrl.slice(prefix.length) || "/")
|
|
242
|
+
: rawUrl;
|
|
239
243
|
const forwardHeaders = stripHopByHop(relayReq.headers);
|
|
240
244
|
forwardHeaders["host"] = `localhost:${this.localPort}`;
|
|
241
245
|
const localReq = httpRequest({
|
|
242
246
|
hostname: "localhost",
|
|
243
247
|
port: this.localPort,
|
|
244
|
-
path:
|
|
248
|
+
path: forwardPath,
|
|
245
249
|
method: relayReq.method,
|
|
246
250
|
headers: forwardHeaders,
|
|
247
251
|
timeout: LOCAL_FORWARD_TIMEOUT_MS,
|