offsite 0.0.32 → 0.0.34

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.
@@ -24,6 +24,7 @@ import { integer, snakeCase, text } from "drizzle-orm/sqlite-core";
24
24
  import { promisify } from "node:util";
25
25
  import webPush from "web-push";
26
26
  import { createProxyServer } from "http-proxy-3";
27
+ import { OUTGOING_PASSES } from "http-proxy-3/dist/lib/http-proxy/passes/web-outgoing.js";
27
28
  import { spawn as spawn$1 } from "@lydell/node-pty";
28
29
  //#region ../core/src/media.ts
29
30
  const mediaTypes = /* @__PURE__ */ new Map([
@@ -890,6 +891,7 @@ function accountSchema(provider) {
890
891
  id: z.string(),
891
892
  provider: z.literal(provider),
892
893
  name: z.string(),
894
+ color: z.string().regex(/^#[0-9a-f]{6}$/i, "must be a hex color like #3b82f6").nullable().default(null),
893
895
  configDir: platformDirs,
894
896
  enabled: z.boolean().default(true),
895
897
  hiddenModels: z.record(z.string(), z.boolean()).default({})
@@ -3584,6 +3586,7 @@ function addAccount(provider, name, configDir) {
3584
3586
  id: randomUUID(),
3585
3587
  provider,
3586
3588
  name: account.name,
3589
+ color: null,
3587
3590
  configDir: { [platform()]: account.configDir },
3588
3591
  enabled: true,
3589
3592
  hiddenModels: {}
@@ -3601,6 +3604,7 @@ async function updateAccount(update) {
3601
3604
  [key]: config[key].map((item) => item.id === update.id ? {
3602
3605
  ...item,
3603
3606
  name: account.name,
3607
+ color: update.color,
3604
3608
  configDir: {
3605
3609
  ...item.configDir,
3606
3610
  [platform()]: account.configDir
@@ -5094,6 +5098,46 @@ function resolveProjectFile(cwd, file) {
5094
5098
  //#endregion
5095
5099
  //#region src/services/proxy.ts
5096
5100
  const proxies = /* @__PURE__ */ new Map();
5101
+ const pageScriptPath = "/__offsite/browser.js";
5102
+ function pageScript(origin) {
5103
+ return `(() => {
5104
+ if (window.parent === window || window.navigation === undefined) {
5105
+ return
5106
+ }
5107
+ const post = (message) =>
5108
+ window.parent.postMessage({ offsite: 'browser', ...message }, '*')
5109
+ const report = () =>
5110
+ post({
5111
+ event: 'location',
5112
+ url: ${JSON.stringify(origin)} + location.pathname + location.search + location.hash,
5113
+ proxied: location.href,
5114
+ back: navigation.canGoBack,
5115
+ forward: navigation.canGoForward,
5116
+ })
5117
+ addEventListener('pageshow', report)
5118
+ navigation.addEventListener('currententrychange', report)
5119
+ navigation.addEventListener('navigate', (event) => {
5120
+ if (!event.destination.sameDocument && event.downloadRequest === null) {
5121
+ post({ event: 'navigate' })
5122
+ }
5123
+ })
5124
+ addEventListener('message', (event) => {
5125
+ if (event.source !== window.parent || event.data?.offsite !== 'browser') {
5126
+ return
5127
+ }
5128
+ if (event.data.command === 'back') {
5129
+ navigation.back()
5130
+ }
5131
+ if (event.data.command === 'forward') {
5132
+ navigation.forward()
5133
+ }
5134
+ if (event.data.command === 'reload') {
5135
+ location.reload()
5136
+ }
5137
+ })
5138
+ })()
5139
+ `;
5140
+ }
5097
5141
  function proxyPort(origin) {
5098
5142
  const target = new URL(origin);
5099
5143
  if (target.protocol !== "http:" && target.protocol !== "https:") throw new Error(`${origin} is not an http or https address`);
@@ -5105,21 +5149,43 @@ function proxyPort(origin) {
5105
5149
  return started;
5106
5150
  }
5107
5151
  function startProxy(target) {
5108
- const proxy = createProxyServer({
5109
- target: target.origin,
5152
+ const options = {
5153
+ target,
5110
5154
  changeOrigin: true,
5111
5155
  ws: true,
5112
5156
  secure: false,
5113
5157
  autoRewrite: true,
5114
5158
  protocolRewrite: "http",
5115
- cookieDomainRewrite: ""
5159
+ cookieDomainRewrite: "",
5160
+ selfHandleResponse: true
5161
+ };
5162
+ const proxy = createProxyServer(options);
5163
+ proxy.on("proxyReq", (outgoing, request) => {
5164
+ rewriteOrigin(target, outgoing, request);
5165
+ if (request.headers.accept?.startsWith("text/html") === true) outgoing.removeHeader("accept-encoding");
5116
5166
  });
5117
- proxy.on("proxyReq", (outgoing, request) => rewriteOrigin(target, outgoing, request));
5118
5167
  proxy.on("proxyReqWs", (outgoing, request) => rewriteOrigin(target, outgoing, request));
5119
- proxy.on("proxyRes", (incoming) => {
5168
+ proxy.on("proxyRes", async (incoming, request, response) => {
5169
+ try {
5170
+ await rewriteRedirect(target, incoming, request);
5171
+ } catch (error) {
5172
+ response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
5173
+ response.end(`Could not follow the redirect from ${target.origin}: ${error instanceof Error ? error.message : String(error)}`);
5174
+ return;
5175
+ }
5120
5176
  delete incoming.headers["x-frame-options"];
5121
5177
  delete incoming.headers["content-security-policy"];
5122
5178
  if (incoming.headers["set-cookie"] !== void 0) incoming.headers["set-cookie"] = incoming.headers["set-cookie"].map((cookie) => cookie.replace(/;\s*secure/i, "").replace(/;\s*samesite=none/i, ""));
5179
+ const page = request.method === "GET" && request.headers.accept?.startsWith("text/html") === true && incoming.statusCode !== 204 && incoming.statusCode !== 304 && /^text\/html/i.test(incoming.headers["content-type"] ?? "") && incoming.headers["content-encoding"] === void 0;
5180
+ if (page) delete incoming.headers["content-length"];
5181
+ for (const pass of Object.values(OUTGOING_PASSES)) pass(request, response, incoming, options);
5182
+ if (!page) {
5183
+ incoming.pipe(response);
5184
+ return;
5185
+ }
5186
+ const chunks = [];
5187
+ incoming.on("data", (chunk) => chunks.push(chunk));
5188
+ incoming.on("end", () => response.end(Buffer.from(Buffer.concat(chunks).toString("latin1").replace(/^(\xEF\xBB\xBF)?\s*(<!doctype[^>]*>)?/i, `$&<script src="${pageScriptPath}"><\/script>`), "latin1")));
5123
5189
  });
5124
5190
  proxy.on("error", (error, _request, response) => {
5125
5191
  if (!(response instanceof http.ServerResponse)) {
@@ -5129,7 +5195,14 @@ function startProxy(target) {
5129
5195
  if (!response.headersSent) response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
5130
5196
  response.end(`Could not reach ${target.origin}: ${error.message}`);
5131
5197
  });
5132
- const server = http.createServer((request, response) => proxy.web(request, response));
5198
+ const server = http.createServer((request, response) => {
5199
+ if (request.url === pageScriptPath) {
5200
+ response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
5201
+ response.end(pageScript(target.origin));
5202
+ return;
5203
+ }
5204
+ proxy.web(request, response);
5205
+ });
5133
5206
  server.on("upgrade", (request, socket, head) => proxy.ws(request, socket, head));
5134
5207
  return new Promise((resolve, reject) => {
5135
5208
  server.once("error", reject);
@@ -5143,6 +5216,16 @@ function startProxy(target) {
5143
5216
  });
5144
5217
  });
5145
5218
  }
5219
+ async function rewriteRedirect(target, incoming, request) {
5220
+ const location = incoming.headers.location;
5221
+ if (location === void 0 || !/^30[1278]$/.test(String(incoming.statusCode))) return;
5222
+ const next = new URL(location, target);
5223
+ if (next.origin === target.origin || next.protocol !== "http:" && next.protocol !== "https:") return;
5224
+ if (request.headers.host === void 0) throw new Error("the redirected request has no host");
5225
+ const proxied = new URL(`http://${request.headers.host}`);
5226
+ proxied.port = String(await proxyPort(next.origin));
5227
+ incoming.headers.location = `${proxied.origin}${next.pathname}${next.search}${next.hash}`;
5228
+ }
5146
5229
  function rewriteOrigin(target, outgoing, request) {
5147
5230
  if (request.headers.origin !== void 0) outgoing.setHeader("origin", target.origin);
5148
5231
  if (request.headers.referer !== void 0) outgoing.setHeader("referer", request.headers.referer.replace(`http://${request.headers.host}`, target.origin));