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