@zaclinley/dsh-mailharbor 0.15.0 → 0.16.0

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/lib/index.js CHANGED
@@ -120,7 +120,7 @@ function transportOptions(settings, credentials, security = {}) {
120
120
  secure,
121
121
  // Plain submission ports must upgrade; never send credentials in the clear.
122
122
  requireTLS: !secure,
123
- auth: credentials.accessToken !== void 0 ? { type: "OAuth2", user: credentials.user, accessToken: credentials.accessToken } : { user: credentials.user, pass: credentials.pass ?? "" },
123
+ auth: { user: credentials.user, pass: credentials.pass ?? "" },
124
124
  tls: { rejectUnauthorized: security.allowSelfSigned !== true, servername: settings.host },
125
125
  connectionTimeout: 2e4,
126
126
  greetingTimeout: 2e4,
@@ -287,7 +287,7 @@ var init_imap = __esm({
287
287
  host: this.settings.host,
288
288
  port: Number(this.settings.port),
289
289
  secure: this.settings.secure !== false,
290
- auth: this.credentials.accessToken !== void 0 ? { user: this.credentials.user, accessToken: this.credentials.accessToken } : { user: this.credentials.user, pass: this.credentials.pass ?? "" },
290
+ auth: { user: this.credentials.user, pass: this.credentials.pass ?? "" },
291
291
  logger: false,
292
292
  tls: { rejectUnauthorized: true },
293
293
  connectionTimeout: this.options.connectionTimeoutMs ?? 15e3,
@@ -710,23 +710,7 @@ var DEFAULTS = {
710
710
  },
711
711
  read: { markOnOpen: true, pollSeconds: 60 },
712
712
  render: { listLimit: 40 },
713
- projects: { root: join2(homedir2(), "work", "projects") },
714
- oauth: {
715
- google: {
716
- clientId: "",
717
- clientSecret: "",
718
- redirectPort: 8765,
719
- scopes: ["https://mail.google.com/"]
720
- },
721
- microsoft: {
722
- clientId: "",
723
- scopes: [
724
- "offline_access",
725
- "https://outlook.office.com/IMAP.AccessAsUser.All",
726
- "https://outlook.office.com/SMTP.Send"
727
- ]
728
- }
729
- }
713
+ projects: { root: join2(homedir2(), "work", "projects") }
730
714
  };
731
715
  var ACCOUNT_ID = /^[A-Za-z0-9_-]{1,64}$/;
732
716
  var EMAIL = /^[^@\s]+@[^@\s]+$/;
@@ -776,7 +760,7 @@ function normalizeAccount(input, index = 0, existing = []) {
776
760
  },
777
761
  keychainService: stringOr(input.keychainService, `dsh-mailharbor:${id}`),
778
762
  dataDir: stringOr(input.dataDir, `accounts/${id}`),
779
- auth: input.auth?.type === "oauth2" ? { type: "oauth2", provider: input.auth.provider } : { type: "password" },
763
+ auth: { type: "password" },
780
764
  caldav: normalizeCaldav(input.caldav),
781
765
  signature: stringOr(input.signature, "").slice(0, 5e3),
782
766
  enabled: input.enabled !== false,
@@ -831,9 +815,6 @@ function loadConfig() {
831
815
  const readRaw = file.read ?? {};
832
816
  const renderRaw = file.render ?? {};
833
817
  const projectsRaw = file.projects ?? {};
834
- const oauthRaw = file.oauth ?? {};
835
- const googleRaw = oauthRaw.google ?? {};
836
- const microsoftRaw = oauthRaw.microsoft ?? {};
837
818
  return {
838
819
  dataDir: DATA_ROOT,
839
820
  configFile: CONFIG_PATH,
@@ -858,18 +839,6 @@ function loadConfig() {
858
839
  projects: {
859
840
  root: stringOr(projectsRaw.root, DEFAULTS.projects.root)
860
841
  },
861
- oauth: {
862
- google: {
863
- clientId: stringOr(googleRaw.clientId, DEFAULTS.oauth.google.clientId),
864
- clientSecret: stringOr(googleRaw.clientSecret, DEFAULTS.oauth.google.clientSecret),
865
- redirectPort: intOr(googleRaw.redirectPort, DEFAULTS.oauth.google.redirectPort, 1024, 65535),
866
- scopes: Array.isArray(googleRaw.scopes) ? googleRaw.scopes.filter((item) => typeof item === "string") : DEFAULTS.oauth.google.scopes
867
- },
868
- microsoft: {
869
- clientId: stringOr(microsoftRaw.clientId, DEFAULTS.oauth.microsoft.clientId),
870
- scopes: Array.isArray(microsoftRaw.scopes) ? microsoftRaw.scopes.filter((item) => typeof item === "string") : DEFAULTS.oauth.microsoft.scopes
871
- }
872
- },
873
842
  revision: Date.now()
874
843
  };
875
844
  }
@@ -907,7 +876,7 @@ function buildAccount(input, existing) {
907
876
  username: input.username,
908
877
  imap: input.imap,
909
878
  smtp: input.smtp,
910
- auth: input.auth ?? { type: "password" },
879
+ auth: { type: "password" },
911
880
  caldav: null,
912
881
  enabled: true,
913
882
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -1018,26 +987,6 @@ async function openPath(path) {
1018
987
  }
1019
988
  await run("xdg-open", [path]);
1020
989
  }
1021
- async function openExternal(url) {
1022
- try {
1023
- const require2 = createRequire(import.meta.url);
1024
- const electron = require2("electron");
1025
- if (electron.shell !== void 0) {
1026
- await electron.shell.openExternal(url);
1027
- return;
1028
- }
1029
- } catch {
1030
- }
1031
- if (process.platform === "darwin") {
1032
- await run("open", [url]);
1033
- return;
1034
- }
1035
- if (process.platform === "win32") {
1036
- await run("cmd", ["/c", "start", "", url]);
1037
- return;
1038
- }
1039
- await run("xdg-open", [url]);
1040
- }
1041
990
 
1042
991
  // src/host/api.ts
1043
992
  import { existsSync as existsSync6, rmSync } from "node:fs";
@@ -1078,134 +1027,6 @@ async function draftMime(account, draft) {
1078
1027
  return Buffer.from(info.message);
1079
1028
  }
1080
1029
 
1081
- // src/host/api.ts
1082
- import { createHash as createHash3, randomBytes } from "node:crypto";
1083
- import { createServer } from "node:http";
1084
-
1085
- // src/host/oauth.ts
1086
- var TOKEN_ENDPOINTS = {
1087
- google: "https://oauth2.googleapis.com/token",
1088
- microsoft: "https://login.microsoftonline.com/common/oauth2/v2.0/token"
1089
- };
1090
- var DEVICE_ENDPOINTS = {
1091
- google: "https://oauth2.googleapis.com/device/code",
1092
- microsoft: "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode"
1093
- };
1094
- function form(values) {
1095
- return new URLSearchParams(values).toString();
1096
- }
1097
- function tokensFrom(payload, previousRefresh = "") {
1098
- const accessToken = typeof payload.access_token === "string" ? payload.access_token : "";
1099
- if (accessToken === "") throw new Error("OAuth \u54CD\u5E94\u7F3A\u5C11 access_token");
1100
- const refreshToken = typeof payload.refresh_token === "string" && payload.refresh_token !== "" ? payload.refresh_token : previousRefresh;
1101
- const expiresIn = Number(payload.expires_in ?? 3600);
1102
- return {
1103
- accessToken,
1104
- refreshToken,
1105
- expiresAt: Date.now() + Math.max(60, expiresIn) * 1e3,
1106
- ...typeof payload.scope === "string" ? { scope: payload.scope } : {},
1107
- ...typeof payload.token_type === "string" ? { tokenType: payload.token_type } : {}
1108
- };
1109
- }
1110
- async function refreshTokens(app, refreshToken) {
1111
- if (refreshToken === "") throw new Error("OAuth \u5237\u65B0\u4EE4\u724C\u4E3A\u7A7A");
1112
- const body = {
1113
- grant_type: "refresh_token",
1114
- client_id: app.clientId,
1115
- refresh_token: refreshToken
1116
- };
1117
- if (app.provider === "google") body.client_secret = app.clientSecret;
1118
- const response = await fetch(TOKEN_ENDPOINTS[app.provider], {
1119
- method: "POST",
1120
- headers: { "content-type": "application/x-www-form-urlencoded" },
1121
- body: form(body)
1122
- });
1123
- const payload = await response.json().catch(() => ({}));
1124
- if (!response.ok) {
1125
- const reason = typeof payload.error_description === "string" ? payload.error_description : typeof payload.error === "string" ? payload.error : `HTTP ${String(response.status)}`;
1126
- throw new Error(`OAuth \u5237\u65B0\u5931\u8D25\uFF1A${reason}`);
1127
- }
1128
- return tokensFrom(payload, refreshToken);
1129
- }
1130
- function googleAuthUrl(app, state, challenge, redirectUri) {
1131
- const params = new URLSearchParams({
1132
- client_id: app.clientId,
1133
- redirect_uri: redirectUri,
1134
- response_type: "code",
1135
- scope: app.scopes.join(" "),
1136
- access_type: "offline",
1137
- prompt: "consent",
1138
- state,
1139
- code_challenge: challenge,
1140
- code_challenge_method: "S256"
1141
- });
1142
- return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
1143
- }
1144
- async function exchangeGoogleCode(app, code, verifier, redirectUri) {
1145
- const response = await fetch(TOKEN_ENDPOINTS.google, {
1146
- method: "POST",
1147
- headers: { "content-type": "application/x-www-form-urlencoded" },
1148
- body: form({
1149
- code,
1150
- client_id: app.clientId,
1151
- client_secret: app.clientSecret,
1152
- redirect_uri: redirectUri,
1153
- grant_type: "authorization_code",
1154
- code_verifier: verifier
1155
- })
1156
- });
1157
- const payload = await response.json().catch(() => ({}));
1158
- if (!response.ok) {
1159
- throw new Error(
1160
- `OAuth \u6388\u6743\u5931\u8D25\uFF1A${typeof payload.error_description === "string" ? payload.error_description : `HTTP ${String(response.status)}`}`
1161
- );
1162
- }
1163
- return tokensFrom(payload);
1164
- }
1165
- async function startDeviceFlow(app) {
1166
- const body = {
1167
- client_id: app.clientId,
1168
- scope: app.scopes.join(" ")
1169
- };
1170
- if (app.provider === "microsoft") body.scope = [...app.scopes, "offline_access"].join(" ");
1171
- const response = await fetch(DEVICE_ENDPOINTS[app.provider], {
1172
- method: "POST",
1173
- headers: { "content-type": "application/x-www-form-urlencoded" },
1174
- body: form(body)
1175
- });
1176
- const payload = await response.json().catch(() => ({}));
1177
- if (!response.ok) {
1178
- throw new Error(
1179
- `OAuth \u8BBE\u5907\u7801\u8BF7\u6C42\u5931\u8D25\uFF1A${typeof payload.error_description === "string" ? payload.error_description : `HTTP ${String(response.status)}`}`
1180
- );
1181
- }
1182
- return {
1183
- deviceCode: String(payload.device_code ?? ""),
1184
- userCode: String(payload.user_code ?? ""),
1185
- verificationUri: String(payload.verification_uri ?? payload.verification_url ?? ""),
1186
- interval: Math.max(2, Number(payload.interval ?? 5)),
1187
- expiresAt: Date.now() + Math.max(60, Number(payload.expires_in ?? 900)) * 1e3
1188
- };
1189
- }
1190
- async function pollDeviceFlow(app, deviceCode) {
1191
- const response = await fetch(TOKEN_ENDPOINTS[app.provider], {
1192
- method: "POST",
1193
- headers: { "content-type": "application/x-www-form-urlencoded" },
1194
- body: form({
1195
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1196
- client_id: app.clientId,
1197
- device_code: deviceCode
1198
- })
1199
- });
1200
- const payload = await response.json().catch(() => ({}));
1201
- if (response.ok) return { status: "ready", tokens: tokensFrom(payload) };
1202
- const error = String(payload.error ?? "");
1203
- if (error === "authorization_pending" || error === "slow_down") return { status: "pending" };
1204
- throw new Error(
1205
- `OAuth \u8BBE\u5907\u7801\u767B\u5F55\u5931\u8D25\uFF1A${typeof payload.error_description === "string" ? payload.error_description : error}`
1206
- );
1207
- }
1208
-
1209
1030
  // src/host/backup.ts
1210
1031
  init_address();
1211
1032
  import { createHash as createHash2 } from "node:crypto";
@@ -1914,7 +1735,7 @@ var BackupEngine = class {
1914
1735
  host: this.runtime.imap.host,
1915
1736
  port: Number(this.runtime.imap.port),
1916
1737
  secure: this.runtime.imap.secure !== false,
1917
- auth: this.credentials.accessToken !== void 0 ? { user: this.credentials.user, accessToken: this.credentials.accessToken } : { user: this.credentials.user, pass: this.credentials.pass ?? "" },
1738
+ auth: { user: this.credentials.user, pass: this.credentials.pass ?? "" },
1918
1739
  logger: false,
1919
1740
  tls: { rejectUnauthorized: true },
1920
1741
  connectionTimeout: 2e4,
@@ -2947,7 +2768,7 @@ ${conversation.trim()}
2947
2768
  }
2948
2769
 
2949
2770
  // src/host/version.ts
2950
- var VERSION = "0.15.0";
2771
+ var VERSION = "0.16.0";
2951
2772
 
2952
2773
  // src/host/api.ts
2953
2774
  init_smtp();
@@ -2998,7 +2819,6 @@ function createApiHandler(runtime) {
2998
2819
  }
2999
2820
  };
3000
2821
  }
3001
- var pendingOAuth = /* @__PURE__ */ new Map();
3002
2822
  function buildRouter(runtime) {
3003
2823
  const router = new Router();
3004
2824
  router.get("/health", () => ({ ok: true, version: VERSION }));
@@ -3177,13 +2997,6 @@ function buildRouter(runtime) {
3177
2997
  provider: config.ai.provider,
3178
2998
  model: config.ai.model,
3179
2999
  summaryLength: config.ai.summaryLength
3180
- },
3181
- oauth: {
3182
- google: {
3183
- clientId: config.oauth.google.clientId,
3184
- redirectPort: config.oauth.google.redirectPort
3185
- },
3186
- microsoft: { clientId: config.oauth.microsoft.clientId }
3187
3000
  }
3188
3001
  },
3189
3002
  accounts: config.accounts.map((account) => ({
@@ -3238,155 +3051,9 @@ function buildRouter(runtime) {
3238
3051
  const ai = asObject(settings.ai);
3239
3052
  if (ai.summaryLength !== void 0) patch.ai = { summaryLength: ai.summaryLength };
3240
3053
  if (Object.keys(patch).length > 0) saveSettings(patch);
3241
- const oauth = asObject(settings.oauth);
3242
- const google = asObject(oauth.google);
3243
- const microsoft = asObject(oauth.microsoft);
3244
- if (typeof google.clientId === "string" || typeof microsoft.clientId === "string") {
3245
- saveSettings({
3246
- oauth: {
3247
- google: {
3248
- clientId: typeof google.clientId === "string" ? google.clientId : config.oauth.google.clientId,
3249
- clientSecret: config.oauth.google.clientSecret,
3250
- redirectPort: typeof google.redirectPort === "number" ? google.redirectPort : config.oauth.google.redirectPort,
3251
- scopes: config.oauth.google.scopes
3252
- },
3253
- microsoft: {
3254
- clientId: typeof microsoft.clientId === "string" ? microsoft.clientId : config.oauth.microsoft.clientId,
3255
- scopes: config.oauth.microsoft.scopes
3256
- }
3257
- }
3258
- });
3259
- }
3260
3054
  runtime.refresh();
3261
3055
  return { ok: true, addedAccounts: added, state: appState(runtime) };
3262
3056
  });
3263
- router.get("/oauth/status", ({ url }) => {
3264
- const { account } = accountFrom(runtime, url);
3265
- const tokens = runtime.oauthTokens(account);
3266
- let appConfigured = false;
3267
- try {
3268
- runtime.oauthApp(account);
3269
- appConfigured = true;
3270
- } catch {
3271
- appConfigured = false;
3272
- }
3273
- return {
3274
- authorized: tokens !== void 0,
3275
- expiresAt: tokens?.expiresAt ?? null,
3276
- provider: account.auth.type === "oauth2" ? account.auth.provider : null,
3277
- appConfigured
3278
- };
3279
- });
3280
- router.post("/oauth/start", async ({ body }) => {
3281
- const payload = await body();
3282
- const account = accountFromPayload(runtime, payload);
3283
- const app = runtime.oauthApp(account);
3284
- const state = randomBytes(16).toString("hex");
3285
- const expiresAt = Date.now() + 10 * 6e4;
3286
- if (app.provider === "microsoft") {
3287
- const device = await startDeviceFlow(app);
3288
- pendingOAuth.set(state, {
3289
- accountId: account.id,
3290
- provider: "microsoft",
3291
- status: "pending",
3292
- deviceCode: device.deviceCode,
3293
- interval: device.interval,
3294
- expiresAt
3295
- });
3296
- return {
3297
- state,
3298
- flow: "microsoft",
3299
- userCode: device.userCode,
3300
- verificationUri: device.verificationUri,
3301
- authUrl: device.verificationUri,
3302
- interval: device.interval
3303
- };
3304
- }
3305
- const verifier = randomBytes(32).toString("base64url");
3306
- const challenge = createHash3("sha256").update(verifier).digest("base64url");
3307
- const redirectUri = `http://127.0.0.1:${String(app.redirectPort)}/oauth/callback`;
3308
- const authUrl = googleAuthUrl(app, state, challenge, redirectUri);
3309
- const entry = {
3310
- accountId: account.id,
3311
- provider: "google",
3312
- status: "pending",
3313
- expiresAt
3314
- };
3315
- pendingOAuth.set(state, entry);
3316
- const server = createServer((request, response) => {
3317
- const target = new URL(request.url ?? "/", "http://127.0.0.1");
3318
- if (target.pathname !== "/oauth/callback") {
3319
- response.writeHead(404).end();
3320
- return;
3321
- }
3322
- const code = target.searchParams.get("code");
3323
- const error = target.searchParams.get("error");
3324
- void (async () => {
3325
- try {
3326
- if (error !== null) throw new Error(error);
3327
- if (code === null) throw new Error("\u7F3A\u5C11\u6388\u6743\u7801");
3328
- const tokens = await exchangeGoogleCode(app, code, verifier, redirectUri);
3329
- runtime.saveOAuthTokens(account, tokens);
3330
- entry.status = "ready";
3331
- response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
3332
- response.end("<p>MailHarbor \u6388\u6743\u6210\u529F\uFF0C\u53EF\u4EE5\u5173\u95ED\u6B64\u9875\u9762\u3002</p>");
3333
- } catch (caught) {
3334
- entry.status = "error";
3335
- entry.message = caught instanceof Error ? caught.message : String(caught);
3336
- response.writeHead(500, { "content-type": "text/html; charset=utf-8" });
3337
- response.end(`<p>\u6388\u6743\u5931\u8D25\uFF1A${entry.message}</p>`);
3338
- } finally {
3339
- setTimeout(() => {
3340
- server.close();
3341
- }, 500);
3342
- }
3343
- })();
3344
- });
3345
- await new Promise((resolve, reject) => {
3346
- server.once("error", reject);
3347
- server.listen(app.redirectPort, "127.0.0.1", () => resolve());
3348
- });
3349
- setTimeout(() => {
3350
- server.close();
3351
- if (entry.status === "pending") {
3352
- entry.status = "error";
3353
- entry.message = "\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5";
3354
- }
3355
- }, 10 * 6e4).unref();
3356
- await openExternal(authUrl).catch(() => void 0);
3357
- return { state, flow: "google", authUrl, expiresAt };
3358
- });
3359
- router.post("/oauth/poll", async ({ body }) => {
3360
- const payload = await body();
3361
- const state = String(payload.state ?? "");
3362
- const entry = pendingOAuth.get(state);
3363
- if (entry === void 0) throw new HttpError(404, "\u6388\u6743\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F");
3364
- if (Date.now() > entry.expiresAt) {
3365
- entry.status = "error";
3366
- entry.message = "\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5";
3367
- }
3368
- if (entry.status !== "pending") {
3369
- if (entry.status === "ready") pendingOAuth.delete(state);
3370
- return { status: entry.status, message: entry.message };
3371
- }
3372
- if (entry.provider === "microsoft" && entry.deviceCode !== void 0) {
3373
- const account = runtime.account(entry.accountId);
3374
- const result = await pollDeviceFlow(runtime.oauthApp(account), entry.deviceCode);
3375
- if (result.status === "ready") {
3376
- runtime.saveOAuthTokens(account, result.tokens);
3377
- pendingOAuth.delete(state);
3378
- return { status: "ready" };
3379
- }
3380
- }
3381
- return { status: "pending" };
3382
- });
3383
- router.post("/oauth/revoke", async ({ body }) => {
3384
- const payload = await body();
3385
- const account = accountFromPayload(runtime, payload);
3386
- runtime.credentials.forgetOAuth(account);
3387
- runtime.invalidate(account.id);
3388
- return { ok: true };
3389
- });
3390
3057
  router.get("/messages/thread", ({ url }) => {
3391
3058
  const { account } = accountFrom(runtime, url);
3392
3059
  const subject = url.searchParams.get("subject") ?? "";
@@ -3519,9 +3186,8 @@ function buildRouter(runtime) {
3519
3186
  const password = typeof input.password === "string" ? input.password : "";
3520
3187
  const config = runtime.current;
3521
3188
  const account = buildAccount(input, config.accounts);
3522
- const oauth = account.auth.type === "oauth2";
3523
- if (!oauth && password === "") throw new HttpError(400, "\u8BF7\u8F93\u5165\u5BC6\u7801\u6216\u5E94\u7528\u4E13\u7528\u5BC6\u7801");
3524
- if (!oauth && input.connect !== false) {
3189
+ if (password === "") throw new HttpError(400, "\u8BF7\u8F93\u5165\u5BC6\u7801\u6216\u5E94\u7528\u4E13\u7528\u5BC6\u7801");
3190
+ if (input.connect !== false) {
3525
3191
  const provider = await verifyImap(account, password);
3526
3192
  await provider.dispose();
3527
3193
  try {
@@ -3529,7 +3195,7 @@ function buildRouter(runtime) {
3529
3195
  } catch {
3530
3196
  }
3531
3197
  }
3532
- if (!oauth) runtime.credentials.remember(account, password, input.remember !== false);
3198
+ runtime.credentials.remember(account, password, input.remember !== false);
3533
3199
  const accounts = [...config.accounts, account];
3534
3200
  saveAccounts(accounts, account.id);
3535
3201
  runtime.refresh();
@@ -4050,23 +3716,6 @@ function buildRouter(runtime) {
4050
3716
  if (payload.ai !== void 0) patch.ai = asObject(payload.ai);
4051
3717
  if (payload.read !== void 0) patch.read = asObject(payload.read);
4052
3718
  if (payload.render !== void 0) patch.render = asObject(payload.render);
4053
- if (payload.oauth !== void 0) {
4054
- const oauth = asObject(payload.oauth);
4055
- const google = asObject(oauth.google);
4056
- const microsoft = asObject(oauth.microsoft);
4057
- patch.oauth = {
4058
- google: {
4059
- clientId: typeof google.clientId === "string" ? google.clientId.trim() : runtime.current.oauth.google.clientId,
4060
- clientSecret: typeof google.clientSecret === "string" ? google.clientSecret.trim() : runtime.current.oauth.google.clientSecret,
4061
- redirectPort: typeof google.redirectPort === "number" ? google.redirectPort : runtime.current.oauth.google.redirectPort,
4062
- scopes: runtime.current.oauth.google.scopes
4063
- },
4064
- microsoft: {
4065
- clientId: typeof microsoft.clientId === "string" ? microsoft.clientId.trim() : runtime.current.oauth.microsoft.clientId,
4066
- scopes: runtime.current.oauth.microsoft.scopes
4067
- }
4068
- };
4069
- }
4070
3719
  if (payload.projects !== void 0) {
4071
3720
  const root = String(asObject(payload.projects).root ?? "").trim();
4072
3721
  if (root === "") throw new HttpError(400, "\u9879\u76EE\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
@@ -4215,8 +3864,7 @@ function settingsView(config) {
4215
3864
  },
4216
3865
  read: { markOnOpen: config.read.markOnOpen, pollSeconds: config.read.pollSeconds },
4217
3866
  render: { listLimit: config.render.listLimit },
4218
- projects: { root: config.projects.root },
4219
- oauth: config.oauth
3867
+ projects: { root: config.projects.root }
4220
3868
  };
4221
3869
  }
4222
3870
  function taskView(task) {
@@ -4593,32 +4241,6 @@ var CredentialManager = class {
4593
4241
  this.session.delete(account.id);
4594
4242
  this.secrets.delete(account.keychainService, account.email);
4595
4243
  }
4596
- oauthService(account) {
4597
- return `${account.keychainService}:oauth`;
4598
- }
4599
- readOAuth(account) {
4600
- const raw = this.secrets.read(this.oauthService(account), account.email);
4601
- if (raw === void 0 || raw.trim() === "") return void 0;
4602
- try {
4603
- const parsed = JSON.parse(raw);
4604
- if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") return void 0;
4605
- return {
4606
- accessToken: parsed.accessToken,
4607
- refreshToken: parsed.refreshToken,
4608
- expiresAt: Number(parsed.expiresAt ?? 0),
4609
- ...typeof parsed.scope === "string" ? { scope: parsed.scope } : {},
4610
- ...typeof parsed.tokenType === "string" ? { tokenType: parsed.tokenType } : {}
4611
- };
4612
- } catch {
4613
- return void 0;
4614
- }
4615
- }
4616
- writeOAuth(account, tokens) {
4617
- return this.secrets.write(this.oauthService(account), account.email, JSON.stringify(tokens));
4618
- }
4619
- forgetOAuth(account) {
4620
- this.secrets.delete(this.oauthService(account), account.email);
4621
- }
4622
4244
  /** Read an arbitrary secret (for example a CalDAV app password). */
4623
4245
  readSecret(service, account) {
4624
4246
  return this.secrets.read(service, account);
@@ -5409,69 +5031,13 @@ var MailHarborRuntime = class {
5409
5031
  paths(account) {
5410
5032
  return accountRuntime(account);
5411
5033
  }
5412
- /** OAuth app configuration for one account's provider. */
5413
- oauthApp(account) {
5414
- if (account.auth.type !== "oauth2") throw new Error("\u8FD9\u4E2A\u8D26\u53F7\u4E0D\u662F OAuth2 \u8D26\u53F7");
5415
- const provider = account.auth.provider;
5416
- const settings = this.current.oauth;
5417
- if (provider === "google") {
5418
- if (settings.google.clientId === "") throw new Error("\u8BF7\u5148\u5728\u8BBE\u7F6E\u91CC\u586B\u5199 Google OAuth Client ID");
5419
- return {
5420
- provider,
5421
- clientId: settings.google.clientId,
5422
- clientSecret: settings.google.clientSecret,
5423
- redirectPort: settings.google.redirectPort,
5424
- scopes: settings.google.scopes.length > 0 ? settings.google.scopes : ["https://mail.google.com/"]
5425
- };
5426
- }
5427
- if (settings.microsoft.clientId === "") throw new Error("\u8BF7\u5148\u5728\u8BBE\u7F6E\u91CC\u586B\u5199 Microsoft OAuth Client ID");
5428
- return {
5429
- provider,
5430
- clientId: settings.microsoft.clientId,
5431
- clientSecret: "",
5432
- redirectPort: 0,
5433
- scopes: settings.microsoft.scopes.length > 0 ? settings.microsoft.scopes : [
5434
- "offline_access",
5435
- "https://outlook.office.com/IMAP.AccessAsUser.All",
5436
- "https://outlook.office.com/SMTP.Send"
5437
- ]
5438
- };
5439
- }
5440
- oauthTokens(account) {
5441
- return this.credentials.readOAuth(account);
5442
- }
5443
- saveOAuthTokens(account, tokens) {
5444
- this.credentials.writeOAuth(account, tokens);
5445
- this.invalidate(account.id);
5446
- }
5447
- /** Refresh when the access token is close to expiring; safe to call often. */
5448
- async refreshOAuth(account) {
5449
- if (account.auth.type !== "oauth2") return void 0;
5450
- const tokens = this.credentials.readOAuth(account);
5451
- if (tokens === void 0) return void 0;
5452
- if (tokens.expiresAt - Date.now() > 10 * 6e4) return tokens;
5453
- const refreshed = await refreshTokens(this.oauthApp(account), tokens.refreshToken);
5454
- this.saveOAuthTokens(account, refreshed);
5455
- return refreshed;
5456
- }
5457
5034
  /** Credentials for an account, with a clear message when the user must log in. */
5458
5035
  credentialsFor(account) {
5459
- if (account.auth.type === "oauth2") {
5460
- const tokens = this.credentials.readOAuth(account);
5461
- if (tokens === void 0) throw new Error("\u8BF7\u5148\u5B8C\u6210 OAuth \u6388\u6743");
5462
- if (tokens.expiresAt !== 0 && tokens.expiresAt <= Date.now()) {
5463
- throw new Error("OAuth \u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u6388\u6743");
5464
- }
5465
- return { user: account.username || account.email, accessToken: tokens.accessToken };
5466
- }
5467
5036
  const resolved = this.credentials.resolve(account);
5468
5037
  if (resolved === void 0) throw new Error("\u8BF7\u5148\u767B\u5F55\u90AE\u7BB1\u8D26\u53F7");
5469
5038
  return { user: account.username || account.email, pass: resolved.password };
5470
5039
  }
5471
5040
  hasCredentials(account) {
5472
- if (account.auth.type === "oauth2") {
5473
- return this.credentials.readOAuth(account) !== void 0;
5474
- }
5475
5041
  return this.credentials.resolve(account) !== void 0;
5476
5042
  }
5477
5043
  imapSettings(account) {
@@ -6034,15 +5600,6 @@ function apply(ctx) {
6034
5600
  }
6035
5601
  const handler = createApiHandler(runtime);
6036
5602
  const disposers = [];
6037
- const refreshOAuth = () => {
6038
- for (const account of runtime.current.accounts) {
6039
- if (account.auth.type !== "oauth2" || !runtime.hasCredentials(account)) continue;
6040
- void runtime.refreshOAuth(account).catch(() => void 0);
6041
- }
6042
- };
6043
- refreshOAuth();
6044
- const oauthTimer = setInterval(refreshOAuth, 15 * 6e4);
6045
- disposers.push(() => clearInterval(oauthTimer));
6046
5603
  const start = () => {
6047
5604
  disposers.push(ctx.webServer.register({ kind: "prefix", path: ROUTE, handler }));
6048
5605
  if (ctx.tools !== void 0) disposers.push(...registerMailTools(ctx.tools, runtime));