unplugin-https-reverse-proxy 3.0.0 → 4.0.1

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/dist/rspack.cjs CHANGED
@@ -13475,33 +13475,63 @@ var caddyTemplate = `{
13475
13475
  }
13476
13476
  }
13477
13477
 
13478
- {% for p in proxies %}
13479
- {{ p.target }}{{ p.portSuffix }} {
13480
- {{ p.tls }}
13481
- reverse_proxy http://{{ p.source }} {% if p.healthCheck %} {
13482
- health_uri {{ p.base }}
13483
- health_interval 2s
13484
- health_timeout 5s
13485
- fail_duration 2s
13486
- unhealthy_status 502 503 504 404
13487
- lb_try_duration 3s
13488
- lb_try_interval 300ms
13478
+ {% for host in hosts %}
13479
+ {{ host.host }}{{ host.portSuffix }} {
13480
+ {{ host.tls }}
13489
13481
 
13490
- @error status 502 503 504 404
13491
- handle_response @error {
13492
- respond * 503 {
13493
- body "Service Unavailable"
13494
- close
13482
+ {% for route in host.routes %}
13483
+ handle {{ route.base }}* {
13484
+ reverse_proxy http://{{ route.source }} {% if route.healthCheck %} {
13485
+ health_uri {{ route.base }}
13486
+ health_interval 2s
13487
+ health_timeout 5s
13488
+ health_headers {
13489
+ User-Agent {http.request.header.User-Agent}
13490
+ Accept text/html,application/json,*/*
13491
+ }
13492
+ fail_duration 2s
13493
+ unhealthy_status 502 503 504 404
13494
+ lb_try_duration 3s
13495
+ lb_try_interval 300ms
13496
+
13497
+ @error status 502 503 504 404
13498
+ handle_response @error {
13499
+ respond * 503 {
13500
+ body "Service Unavailable"
13501
+ close
13502
+ }
13495
13503
  }
13496
13504
  }
13505
+ {% endif %}
13497
13506
  }
13498
- {% endif %}
13507
+ {% endfor %}
13499
13508
  }
13500
13509
  {% endfor %}
13501
13510
  `;
13502
13511
  var liquid = new Liquid();
13503
13512
  async function genCaddyfile(ctx) {
13504
- return await liquid.parseAndRender(caddyTemplate, ctx);
13513
+ const hostsMap = /* @__PURE__ */ new Map();
13514
+ for (const proxy of ctx.proxies) {
13515
+ const key = `${proxy.target}${proxy.portSuffix}`;
13516
+ if (!hostsMap.has(key)) {
13517
+ hostsMap.set(key, {
13518
+ host: proxy.target,
13519
+ portSuffix: proxy.portSuffix,
13520
+ tls: proxy.tls,
13521
+ routes: []
13522
+ });
13523
+ }
13524
+ let base = proxy.base || "/";
13525
+ if (!base.startsWith("/"))
13526
+ base = `/${base}`;
13527
+ if (base !== "/" && !base.endsWith("/"))
13528
+ base = `${base}/`;
13529
+ hostsMap.get(key).routes.push({ ...proxy, base });
13530
+ }
13531
+ for (const host of hostsMap.values())
13532
+ host.routes.sort((a, b) => b.base.length - a.base.length);
13533
+ const hosts = Array.from(hostsMap.values());
13534
+ return await liquid.parseAndRender(caddyTemplate, { ...ctx, hosts });
13505
13535
  }
13506
13536
 
13507
13537
  // src/caddy/config.ts
@@ -13666,8 +13696,13 @@ function useCaddyConfig() {
13666
13696
  function writeCaddyConfig(_proxies) {
13667
13697
  const proxies = configJsonRef() || {};
13668
13698
  _proxies.forEach((p) => {
13669
- const index = `${p.target}${p.portSuffix}`;
13670
- proxies[index] = p;
13699
+ let base = p.base || "/";
13700
+ if (!base.startsWith("/"))
13701
+ base = `/${base}`;
13702
+ if (base !== "/" && !base.endsWith("/"))
13703
+ base = `${base}/`;
13704
+ const index = `${p.target}${p.portSuffix}${base}`;
13705
+ proxies[index] = { ...p, base };
13671
13706
  });
13672
13707
  const content = JSON.stringify(proxies, null, 2);
13673
13708
  configContentRef(content);
@@ -13885,13 +13920,22 @@ var CaddyInstant = class {
13885
13920
  throw new Error("update host failed");
13886
13921
  consola.success("update host success\n");
13887
13922
  const targetHost = target.split(":")[0];
13923
+ const normalizeBase = (b) => {
13924
+ let out = b || "/";
13925
+ if (!out.startsWith("/"))
13926
+ out = `/${out}`;
13927
+ if (out !== "/" && !out.endsWith("/"))
13928
+ out = `${out}/`;
13929
+ return out;
13930
+ };
13931
+ const baseNormalized = normalizeBase(base);
13888
13932
  const proxies = [
13889
13933
  {
13890
13934
  target: targetHost,
13891
13935
  portSuffix: https ? "" : ":80",
13892
13936
  tls: https ? "tls internal" : "",
13893
13937
  source,
13894
- base,
13938
+ base: baseNormalized,
13895
13939
  healthCheck
13896
13940
  }
13897
13941
  ];
@@ -13901,14 +13945,12 @@ var CaddyInstant = class {
13901
13945
  effect(async () => {
13902
13946
  const { configJsonRef } = getCaddyConfig();
13903
13947
  const proxies2 = Object.values(configJsonRef() || {});
13904
- if (!(this.caddyfile || "").includes(`${target.split(":")[0]}${https ? "" : ":80"}`)) {
13905
- const ctx = validateTemplateContext({
13906
- caddy_root: this.caddyRoot,
13907
- proxies: proxies2
13908
- });
13909
- this.caddyfile = await genCaddyfile(ctx);
13910
- await this.writeCaddyfile();
13911
- }
13948
+ const ctx = validateTemplateContext({
13949
+ caddy_root: this.caddyRoot,
13950
+ proxies: proxies2
13951
+ });
13952
+ this.caddyfile = await genCaddyfile(ctx);
13953
+ await this.writeCaddyfile();
13912
13954
  })
13913
13955
  );
13914
13956
  if (!(0, import_node_fs3.existsSync)(caddyLockFilePath))
@@ -14044,9 +14086,238 @@ ${line}
14044
14086
  }
14045
14087
  };
14046
14088
 
14089
+ // src/hmr-sse-bridge.ts
14090
+ var SSE_PATH = "/__hmr_sse";
14091
+ var SEND_PATH = "/__hmr_send";
14092
+ var MAX_POST_BODY = 64 * 1024;
14093
+ function setupHmrSseBridge(server) {
14094
+ const sseClients = /* @__PURE__ */ new Set();
14095
+ const originalSend = server.ws.send.bind(server.ws);
14096
+ server.ws.send = (...args2) => {
14097
+ ;
14098
+ originalSend(...args2);
14099
+ let payload;
14100
+ if (typeof args2[0] === "string") {
14101
+ payload = JSON.stringify({ type: "custom", event: args2[0], data: args2[1] });
14102
+ } else {
14103
+ payload = JSON.stringify(args2[0]);
14104
+ }
14105
+ for (const res of sseClients) {
14106
+ try {
14107
+ res.write(`data: ${payload}
14108
+
14109
+ `);
14110
+ } catch {
14111
+ sseClients.delete(res);
14112
+ }
14113
+ }
14114
+ };
14115
+ server.middlewares.use((req, res, next2) => {
14116
+ var _a4;
14117
+ const url2 = (_a4 = req.url) == null ? void 0 : _a4.split("?")[0];
14118
+ if (url2 === SSE_PATH && req.method === "GET") {
14119
+ handleSseConnection(req, res, sseClients);
14120
+ return;
14121
+ }
14122
+ if (url2 === SEND_PATH && req.method === "POST") {
14123
+ handleClientMessage(req, res, server);
14124
+ return;
14125
+ }
14126
+ next2();
14127
+ });
14128
+ }
14129
+ function handleSseConnection(_req, res, sseClients) {
14130
+ res.writeHead(200, {
14131
+ "Content-Type": "text/event-stream",
14132
+ "Cache-Control": "no-cache",
14133
+ "Connection": "keep-alive"
14134
+ });
14135
+ res.write(`data: ${JSON.stringify({ type: "connected" })}
14136
+
14137
+ `);
14138
+ sseClients.add(res);
14139
+ const keepAlive = setInterval(() => {
14140
+ try {
14141
+ res.write(": ping\n\n");
14142
+ } catch {
14143
+ cleanup();
14144
+ }
14145
+ }, 3e4);
14146
+ function cleanup() {
14147
+ clearInterval(keepAlive);
14148
+ sseClients.delete(res);
14149
+ }
14150
+ res.on("close", cleanup);
14151
+ res.on("error", cleanup);
14152
+ }
14153
+ function handleClientMessage(req, res, server) {
14154
+ let body = "";
14155
+ let overflow = false;
14156
+ req.on("data", (chunk) => {
14157
+ body += chunk.toString();
14158
+ if (body.length > MAX_POST_BODY) {
14159
+ overflow = true;
14160
+ req.destroy();
14161
+ }
14162
+ });
14163
+ req.on("end", () => {
14164
+ var _a4, _b3, _c2, _d2, _e;
14165
+ if (overflow) {
14166
+ res.writeHead(413);
14167
+ res.end('{"ok":false}');
14168
+ return;
14169
+ }
14170
+ try {
14171
+ const data = JSON.parse(body);
14172
+ if (data.type === "custom" && data.event) {
14173
+ const dummyClient = {
14174
+ send: (...args2) => {
14175
+ server.ws.send(...args2);
14176
+ }
14177
+ };
14178
+ (_e = (_d2 = (_c2 = (_b3 = (_a4 = server.environments) == null ? void 0 : _a4.client) == null ? void 0 : _b3.hot) == null ? void 0 : _c2.api) == null ? void 0 : _d2.emit) == null ? void 0 : _e.call(_d2, data.event, data.data, dummyClient);
14179
+ }
14180
+ res.writeHead(200, {
14181
+ "Content-Type": "application/json"
14182
+ });
14183
+ res.end('{"ok":true}');
14184
+ } catch {
14185
+ res.writeHead(400);
14186
+ res.end('{"ok":false}');
14187
+ }
14188
+ });
14189
+ }
14190
+ var hmrSseBridgeClientScript = `
14191
+ (function() {
14192
+ // Activate when served over HTTPS to bypass WSS self-signed cert issues
14193
+ if (location.protocol !== 'https:') return;
14194
+
14195
+ var OriginalWebSocket = window.WebSocket;
14196
+
14197
+ function SseWebSocket(originalUrl) {
14198
+ var self = this;
14199
+ // WebSocket readyState constants on instance
14200
+ self.CONNECTING = 0;
14201
+ self.OPEN = 1;
14202
+ self.CLOSING = 2;
14203
+ self.CLOSED = 3;
14204
+ self.readyState = 0;
14205
+ self.onopen = null;
14206
+ self.onmessage = null;
14207
+ self.onclose = null;
14208
+ self.onerror = null;
14209
+ self.bufferedAmount = 0;
14210
+ self.extensions = '';
14211
+ self.binaryType = 'blob';
14212
+ self.protocol = 'vite-hmr';
14213
+ self.url = originalUrl;
14214
+ self._listeners = {};
14215
+ self._opened = false;
14216
+
14217
+ var sseUrl = location.origin + '${SSE_PATH}';
14218
+ var sendUrl = location.origin + '${SEND_PATH}';
14219
+ self._sendUrl = sendUrl;
14220
+
14221
+ var es = new EventSource(sseUrl);
14222
+ self._es = es;
14223
+
14224
+ es.onopen = function() {
14225
+ if (self._opened) return;
14226
+ self._opened = true;
14227
+ self.readyState = 1;
14228
+ var evt = new Event('open');
14229
+ if (self.onopen) self.onopen(evt);
14230
+ self._emit('open', evt);
14231
+ };
14232
+
14233
+ es.onmessage = function(e) {
14234
+ var evt = new MessageEvent('message', { data: e.data });
14235
+ if (self.onmessage) self.onmessage(evt);
14236
+ self._emit('message', evt);
14237
+ };
14238
+
14239
+ es.onerror = function() {
14240
+ // EventSource auto-reconnects on error; don't treat as fatal close
14241
+ // Only emit error event, never close event (SSE handles reconnect internally)
14242
+ if (self.readyState >= 2) return;
14243
+ // EventSource auto-reconnects; no action needed
14244
+ };
14245
+ }
14246
+
14247
+ SseWebSocket.prototype.send = function(data) {
14248
+ if (this.readyState !== 1) return;
14249
+ fetch(this._sendUrl, {
14250
+ method: 'POST',
14251
+ headers: { 'Content-Type': 'application/json' },
14252
+ body: data
14253
+ }).catch(function() {});
14254
+ };
14255
+
14256
+ SseWebSocket.prototype.close = function() {
14257
+ if (this.readyState >= 2) return;
14258
+ this.readyState = 3;
14259
+ if (this._es) this._es.close();
14260
+ var evt = new CloseEvent('close', { code: 1000, reason: '', wasClean: true });
14261
+ if (this.onclose) this.onclose(evt);
14262
+ this._emit('close', evt);
14263
+ };
14264
+
14265
+ SseWebSocket.prototype.addEventListener = function(type, fn, options) {
14266
+ if (!this._listeners[type]) this._listeners[type] = [];
14267
+ var entry = { fn: fn, once: !!(options && options.once) };
14268
+ this._listeners[type].push(entry);
14269
+ };
14270
+
14271
+ SseWebSocket.prototype.removeEventListener = function(type, fn) {
14272
+ var list = this._listeners[type];
14273
+ if (!list) return;
14274
+ this._listeners[type] = list.filter(function(e) { return e.fn !== fn; });
14275
+ };
14276
+
14277
+ SseWebSocket.prototype.dispatchEvent = function(evt) {
14278
+ this._emit(evt.type, evt);
14279
+ return true;
14280
+ };
14281
+
14282
+ SseWebSocket.prototype._emit = function(type, evt) {
14283
+ var list = this._listeners[type];
14284
+ if (!list) return;
14285
+ var remaining = [];
14286
+ list.forEach(function(entry) {
14287
+ entry.fn(evt);
14288
+ if (!entry.once) remaining.push(entry);
14289
+ });
14290
+ this._listeners[type] = remaining;
14291
+ };
14292
+
14293
+ function PatchedWebSocket(url, protocols) {
14294
+ // Only intercept Vite HMR connections (protocol 'vite-hmr')
14295
+ var isViteHmr = protocols === 'vite-hmr'
14296
+ || (Array.isArray(protocols) && protocols.indexOf('vite-hmr') !== -1);
14297
+ if (!isViteHmr) {
14298
+ if (protocols !== undefined) {
14299
+ return new OriginalWebSocket(url, protocols);
14300
+ }
14301
+ return new OriginalWebSocket(url);
14302
+ }
14303
+ return new SseWebSocket(url);
14304
+ }
14305
+
14306
+ PatchedWebSocket.CONNECTING = 0;
14307
+ PatchedWebSocket.OPEN = 1;
14308
+ PatchedWebSocket.CLOSING = 2;
14309
+ PatchedWebSocket.CLOSED = 3;
14310
+ PatchedWebSocket.prototype = Object.create(OriginalWebSocket.prototype);
14311
+ PatchedWebSocket.prototype.constructor = PatchedWebSocket;
14312
+
14313
+ window.WebSocket = PatchedWebSocket;
14314
+ })();
14315
+ `;
14316
+
14047
14317
  // src/index.ts
14048
14318
  var config;
14049
14319
  var caddy;
14320
+ var hmrSseBridgeActive = false;
14050
14321
  var cwd = import_node_process5.default.cwd();
14051
14322
  function colorUrl(url2) {
14052
14323
  return import_picocolors2.default.green(url2.replace(/:(\d+)\//, (_, port) => `:${import_picocolors2.default.bold(port)}/`));
@@ -14133,10 +14404,24 @@ var unpluginFactory = (options) => ({
14133
14404
  }
14134
14405
  const base = server.config.base || "/";
14135
14406
  server.printUrls = () => vitePrintUrls(options, source, target, base, _printUrls);
14407
+ if (options.https) {
14408
+ setupHmrSseBridge(server);
14409
+ hmrSseBridgeActive = true;
14410
+ consola.info("HMR SSE bridge enabled for iOS Safari WSS workaround");
14411
+ }
14412
+ },
14413
+ transformIndexHtml() {
14414
+ if (!hmrSseBridgeActive)
14415
+ return [];
14416
+ return [{
14417
+ tag: "script",
14418
+ children: hmrSseBridgeClientScript,
14419
+ injectTo: "head-prepend"
14420
+ }];
14136
14421
  }
14137
14422
  },
14138
14423
  webpack(compiler) {
14139
- var _a4, _b3;
14424
+ var _a4, _b3, _c2;
14140
14425
  if (import_node_process5.default.env.NODE_ENV !== "development")
14141
14426
  return;
14142
14427
  const {
@@ -14167,7 +14452,16 @@ var unpluginFactory = (options) => ({
14167
14452
  const source = `${devServer.host}:${devServer.port}`;
14168
14453
  if (caddy)
14169
14454
  return;
14170
- const _close = (_b3 = compiler.close) == null ? void 0 : _b3.bind(compiler);
14455
+ let base = "/";
14456
+ const publicPath = (_b3 = compiler.options.output) == null ? void 0 : _b3.publicPath;
14457
+ if (typeof publicPath === "string" && publicPath) {
14458
+ base = publicPath;
14459
+ }
14460
+ if (!base.startsWith("/"))
14461
+ base = `/${base}`;
14462
+ if (base !== "/" && !base.endsWith("/"))
14463
+ base = `${base}/`;
14464
+ const _close = (_c2 = compiler.close) == null ? void 0 : _c2.bind(compiler);
14171
14465
  if (_close) {
14172
14466
  compiler.close = async (callback) => {
14173
14467
  import_node_process5.default.on("exit", () => {
@@ -14179,18 +14473,19 @@ var unpluginFactory = (options) => ({
14179
14473
  }
14180
14474
  caddy = new CaddyInstant();
14181
14475
  caddy.run(source, target, {
14476
+ base,
14182
14477
  ...options
14183
14478
  }).then(() => {
14184
14479
  compiler.hooks.done.tap("unplugin-https-reverse-proxy", async () => {
14185
14480
  await printForwardProxy();
14186
- consola.success(` ${import_picocolors2.default.green("\u279C")} ${import_picocolors2.default.bold("run caddy reverse proxy success")}: ${colorUrl(`${options.https ? "https" : "http"}://${target}`)}`);
14481
+ consola.success(` ${import_picocolors2.default.green("\u279C")} ${import_picocolors2.default.bold("run caddy reverse proxy success")}: ${colorUrl(`${options.https ? "https" : "http"}://${target}${base}`)}`);
14187
14482
  });
14188
14483
  }).catch((e) => {
14189
14484
  throw e;
14190
14485
  });
14191
14486
  },
14192
14487
  async rspack(compiler) {
14193
- var _a4, _b3, _c2;
14488
+ var _a4, _b3, _c2, _d2;
14194
14489
  if (import_node_process5.default.env.NODE_ENV !== "development")
14195
14490
  return;
14196
14491
  const {
@@ -14223,11 +14518,21 @@ var unpluginFactory = (options) => ({
14223
14518
  if (caddy)
14224
14519
  return;
14225
14520
  caddy = new CaddyInstant();
14521
+ let base = "/";
14522
+ const publicPath = (_d2 = compiler.options.output) == null ? void 0 : _d2.publicPath;
14523
+ if (typeof publicPath === "string" && publicPath) {
14524
+ base = publicPath;
14525
+ }
14526
+ if (!base.startsWith("/"))
14527
+ base = `/${base}`;
14528
+ if (base !== "/" && !base.endsWith("/"))
14529
+ base = `${base}/`;
14226
14530
  caddy.run(source, target, {
14531
+ base,
14227
14532
  ...options
14228
14533
  }).then(async () => {
14229
14534
  await printForwardProxy();
14230
- consola.success(` ${import_picocolors2.default.green("\u279C")} ${import_picocolors2.default.bold("run caddy reverse proxy success")}: ${colorUrl(`${options.https ? "https" : "http"}://${target}`)}`);
14535
+ consola.success(` ${import_picocolors2.default.green("\u279C")} ${import_picocolors2.default.bold("run caddy reverse proxy success")}: ${colorUrl(`${options.https ? "https" : "http"}://${target}${base}`)}`);
14231
14536
  }).catch((e) => {
14232
14537
  throw e;
14233
14538
  });
package/dist/rspack.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  src_default
3
- } from "./chunk-5MC5SB7O.js";
3
+ } from "./chunk-QURDG7SD.js";
4
+ import "./chunk-RSBXFJ3Z.js";
4
5
  import "./chunk-2P7SUCQ3.js";
5
6
 
6
7
  // src/rspack.ts