offsite 0.0.32 → 0.0.33

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({})
@@ -3458,6 +3460,14 @@ function openCodeRuns() {
3458
3460
  }
3459
3461
  //#endregion
3460
3462
  //#region src/services/accounts.ts
3463
+ const accountColors = [
3464
+ "#3b82f6",
3465
+ "#16a34a",
3466
+ "#f97316",
3467
+ "#a855f7",
3468
+ "#ec4899",
3469
+ "#0d9488"
3470
+ ];
3461
3471
  const providerNames = {
3462
3472
  claude: "Claude",
3463
3473
  codex: "Codex",
@@ -3468,13 +3478,11 @@ function providerName(provider) {
3468
3478
  }
3469
3479
  function storedAccounts() {
3470
3480
  const config = readConfig();
3471
- return [...config.claudeAccounts.map((item) => ({
3472
- ...item,
3473
- configDir: item.configDir[platform()]
3474
- })), ...config.codexAccounts.map((item) => ({
3481
+ return [...config.claudeAccounts, ...config.codexAccounts].map((item, index) => ({
3475
3482
  ...item,
3483
+ color: item.color ?? accountColors[index % accountColors.length],
3476
3484
  configDir: item.configDir[platform()]
3477
- }))];
3485
+ }));
3478
3486
  }
3479
3487
  function assertEnabled(provider, accountId) {
3480
3488
  if (!readConfig().enabledProviders[provider]) throw new Error(`${providerName(provider)} is disabled in Settings`);
@@ -3576,6 +3584,8 @@ function providerCapabilities(provider, accountId, cwd) {
3576
3584
  }
3577
3585
  function addAccount(provider, name, configDir) {
3578
3586
  const account = validAccount(name, configDir);
3587
+ const stored = storedAccounts();
3588
+ const color = accountColors.find((item) => stored.every((other) => other.color !== item)) ?? accountColors[stored.length % accountColors.length];
3579
3589
  const config = readConfig();
3580
3590
  const key = `${provider}Accounts`;
3581
3591
  saveConfig({
@@ -3584,6 +3594,7 @@ function addAccount(provider, name, configDir) {
3584
3594
  id: randomUUID(),
3585
3595
  provider,
3586
3596
  name: account.name,
3597
+ color,
3587
3598
  configDir: { [platform()]: account.configDir },
3588
3599
  enabled: true,
3589
3600
  hiddenModels: {}
@@ -3601,6 +3612,7 @@ async function updateAccount(update) {
3601
3612
  [key]: config[key].map((item) => item.id === update.id ? {
3602
3613
  ...item,
3603
3614
  name: account.name,
3615
+ color: update.color,
3604
3616
  configDir: {
3605
3617
  ...item.configDir,
3606
3618
  [platform()]: account.configDir
@@ -5094,6 +5106,46 @@ function resolveProjectFile(cwd, file) {
5094
5106
  //#endregion
5095
5107
  //#region src/services/proxy.ts
5096
5108
  const proxies = /* @__PURE__ */ new Map();
5109
+ const pageScriptPath = "/__offsite/browser.js";
5110
+ function pageScript(origin) {
5111
+ return `(() => {
5112
+ if (window.parent === window || window.navigation === undefined) {
5113
+ return
5114
+ }
5115
+ const post = (message) =>
5116
+ window.parent.postMessage({ offsite: 'browser', ...message }, '*')
5117
+ const report = () =>
5118
+ post({
5119
+ event: 'location',
5120
+ url: ${JSON.stringify(origin)} + location.pathname + location.search + location.hash,
5121
+ proxied: location.href,
5122
+ back: navigation.canGoBack,
5123
+ forward: navigation.canGoForward,
5124
+ })
5125
+ addEventListener('pageshow', report)
5126
+ navigation.addEventListener('currententrychange', report)
5127
+ navigation.addEventListener('navigate', (event) => {
5128
+ if (!event.destination.sameDocument && event.downloadRequest === null) {
5129
+ post({ event: 'navigate' })
5130
+ }
5131
+ })
5132
+ addEventListener('message', (event) => {
5133
+ if (event.source !== window.parent || event.data?.offsite !== 'browser') {
5134
+ return
5135
+ }
5136
+ if (event.data.command === 'back') {
5137
+ navigation.back()
5138
+ }
5139
+ if (event.data.command === 'forward') {
5140
+ navigation.forward()
5141
+ }
5142
+ if (event.data.command === 'reload') {
5143
+ location.reload()
5144
+ }
5145
+ })
5146
+ })()
5147
+ `;
5148
+ }
5097
5149
  function proxyPort(origin) {
5098
5150
  const target = new URL(origin);
5099
5151
  if (target.protocol !== "http:" && target.protocol !== "https:") throw new Error(`${origin} is not an http or https address`);
@@ -5105,21 +5157,43 @@ function proxyPort(origin) {
5105
5157
  return started;
5106
5158
  }
5107
5159
  function startProxy(target) {
5108
- const proxy = createProxyServer({
5109
- target: target.origin,
5160
+ const options = {
5161
+ target,
5110
5162
  changeOrigin: true,
5111
5163
  ws: true,
5112
5164
  secure: false,
5113
5165
  autoRewrite: true,
5114
5166
  protocolRewrite: "http",
5115
- cookieDomainRewrite: ""
5167
+ cookieDomainRewrite: "",
5168
+ selfHandleResponse: true
5169
+ };
5170
+ const proxy = createProxyServer(options);
5171
+ proxy.on("proxyReq", (outgoing, request) => {
5172
+ rewriteOrigin(target, outgoing, request);
5173
+ if (request.headers.accept?.startsWith("text/html") === true) outgoing.removeHeader("accept-encoding");
5116
5174
  });
5117
- proxy.on("proxyReq", (outgoing, request) => rewriteOrigin(target, outgoing, request));
5118
5175
  proxy.on("proxyReqWs", (outgoing, request) => rewriteOrigin(target, outgoing, request));
5119
- proxy.on("proxyRes", (incoming) => {
5176
+ proxy.on("proxyRes", async (incoming, request, response) => {
5177
+ try {
5178
+ await rewriteRedirect(target, incoming, request);
5179
+ } catch (error) {
5180
+ response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
5181
+ response.end(`Could not follow the redirect from ${target.origin}: ${error instanceof Error ? error.message : String(error)}`);
5182
+ return;
5183
+ }
5120
5184
  delete incoming.headers["x-frame-options"];
5121
5185
  delete incoming.headers["content-security-policy"];
5122
5186
  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, ""));
5187
+ 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;
5188
+ if (page) delete incoming.headers["content-length"];
5189
+ for (const pass of Object.values(OUTGOING_PASSES)) pass(request, response, incoming, options);
5190
+ if (!page) {
5191
+ incoming.pipe(response);
5192
+ return;
5193
+ }
5194
+ const chunks = [];
5195
+ incoming.on("data", (chunk) => chunks.push(chunk));
5196
+ 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
5197
  });
5124
5198
  proxy.on("error", (error, _request, response) => {
5125
5199
  if (!(response instanceof http.ServerResponse)) {
@@ -5129,7 +5203,14 @@ function startProxy(target) {
5129
5203
  if (!response.headersSent) response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
5130
5204
  response.end(`Could not reach ${target.origin}: ${error.message}`);
5131
5205
  });
5132
- const server = http.createServer((request, response) => proxy.web(request, response));
5206
+ const server = http.createServer((request, response) => {
5207
+ if (request.url === pageScriptPath) {
5208
+ response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
5209
+ response.end(pageScript(target.origin));
5210
+ return;
5211
+ }
5212
+ proxy.web(request, response);
5213
+ });
5133
5214
  server.on("upgrade", (request, socket, head) => proxy.ws(request, socket, head));
5134
5215
  return new Promise((resolve, reject) => {
5135
5216
  server.once("error", reject);
@@ -5143,6 +5224,16 @@ function startProxy(target) {
5143
5224
  });
5144
5225
  });
5145
5226
  }
5227
+ async function rewriteRedirect(target, incoming, request) {
5228
+ const location = incoming.headers.location;
5229
+ if (location === void 0 || !/^30[1278]$/.test(String(incoming.statusCode))) return;
5230
+ const next = new URL(location, target);
5231
+ if (next.origin === target.origin || next.protocol !== "http:" && next.protocol !== "https:") return;
5232
+ if (request.headers.host === void 0) throw new Error("the redirected request has no host");
5233
+ const proxied = new URL(`http://${request.headers.host}`);
5234
+ proxied.port = String(await proxyPort(next.origin));
5235
+ incoming.headers.location = `${proxied.origin}${next.pathname}${next.search}${next.hash}`;
5236
+ }
5146
5237
  function rewriteOrigin(target, outgoing, request) {
5147
5238
  if (request.headers.origin !== void 0) outgoing.setHeader("origin", target.origin);
5148
5239
  if (request.headers.referer !== void 0) outgoing.setHeader("referer", request.headers.referer.replace(`http://${request.headers.host}`, target.origin));