@bobfrankston/mailx-settings 0.1.33 → 0.1.35
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/cloud.d.ts +17 -10
- package/cloud.d.ts.map +1 -1
- package/cloud.js +161 -11
- package/docs/preferences.md +1 -0
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +34 -20
- package/package.json +3 -3
package/cloud.d.ts
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* which prevents conflicts with existing folders of the same name.
|
|
7
7
|
* All machines using the same OAuth client ID share the same folder.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* OneDrive (restored 2026-07-12): Microsoft Graph, PATH-addressed (no folder
|
|
10
|
+
* IDs — `root:/{path}:/content`), so the whole GDrive folder-ID
|
|
11
|
+
* discovery/validation/ownership machinery has no OneDrive counterpart.
|
|
12
|
+
* Needs ~/.rmfmail/microsoft-credentials.json (Azure public-client app;
|
|
13
|
+
* same registration the Outlook Graph mail provider uses — different
|
|
14
|
+
* scopes on the same token infrastructure).
|
|
15
15
|
*
|
|
16
16
|
* Dropbox (removed 2026-04-06): Never implemented — placeholder only.
|
|
17
17
|
* Would need Dropbox OAuth app, token management, and Dropbox API v2 calls.
|
|
@@ -46,7 +46,12 @@ export declare function gDriveValidateCachedFolder(folderId: string): Promise<bo
|
|
|
46
46
|
* orphaning the user's accounts/contacts/etc. Switched to the dotted name
|
|
47
47
|
* matching the actual folder convention. */
|
|
48
48
|
export declare function gDriveFindOrCreateFolder(): Promise<string | null>;
|
|
49
|
-
|
|
49
|
+
/** Fetch the authenticated Microsoft user's profile (setup-flow display). */
|
|
50
|
+
export declare function getMicrosoftProfile(): Promise<{
|
|
51
|
+
name?: string;
|
|
52
|
+
email?: string;
|
|
53
|
+
} | null>;
|
|
54
|
+
export type CloudProvider = "gdrive" | "google" | "onedrive" | "local";
|
|
50
55
|
export interface CloudFile {
|
|
51
56
|
/** Read a file. For gdrive, path is just the filename (folder ID is implicit). */
|
|
52
57
|
read(filePath: string): Promise<string | null>;
|
|
@@ -58,10 +63,12 @@ export interface CloudFile {
|
|
|
58
63
|
exists(filePath: string): Promise<boolean>;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
|
-
* Get a cloud file provider.
|
|
62
|
-
*
|
|
66
|
+
* Get a cloud file provider. The locator is provider-specific: gdrive takes
|
|
67
|
+
* the folder ID; onedrive takes the base PATH under the drive root (e.g.
|
|
68
|
+
* "home/.rmfmail"). Files are stored flat in that folder either way — the
|
|
69
|
+
* CloudFile contract is identical, only the FS API underneath differs.
|
|
63
70
|
*/
|
|
64
|
-
export declare function getCloudProvider(provider: string,
|
|
71
|
+
export declare function getCloudProvider(provider: string, locator?: string): CloudFile | null;
|
|
65
72
|
/** Fetch the authenticated Google user's profile (name + email) via the People API.
|
|
66
73
|
* Caller must supply an OAuth access token whose scopes include `contacts.readonly`
|
|
67
74
|
* or `userinfo.profile`. The Drive token (drive.file) does NOT have the right scope
|
package/cloud.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+RH;;;;;;;;;oDASoD;AACpD,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BnF;AAcD;;;;;;;;;;;;;;;;;6CAiB6C;AAC7C,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBvE;
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+RH;;;;;;;;;oDASoD;AACpD,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BnF;AAcD;;;;;;;;;;;;;;;;;6CAiB6C;AAC7C,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBvE;AA8OD,6EAA6E;AAC7E,wBAAsB,mBAAmB,IAAI,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAS7F;AAID,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAEvE,MAAM,WAAW,SAAS;IACtB,kFAAkF;IAClF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,8DAA8D;IAC9D,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrD,uGAAuG;IACvG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,8BAA8B;IAC9B,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAoCrF;AAED;;;;2DAI2D;AAC3D,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoBvG"}
|
package/cloud.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* which prevents conflicts with existing folders of the same name.
|
|
7
7
|
* All machines using the same OAuth client ID share the same folder.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* OneDrive (restored 2026-07-12): Microsoft Graph, PATH-addressed (no folder
|
|
10
|
+
* IDs — `root:/{path}:/content`), so the whole GDrive folder-ID
|
|
11
|
+
* discovery/validation/ownership machinery has no OneDrive counterpart.
|
|
12
|
+
* Needs ~/.rmfmail/microsoft-credentials.json (Azure public-client app;
|
|
13
|
+
* same registration the Outlook Graph mail provider uses — different
|
|
14
|
+
* scopes on the same token infrastructure).
|
|
15
15
|
*
|
|
16
16
|
* Dropbox (removed 2026-04-06): Never implemented — placeholder only.
|
|
17
17
|
* Would need Dropbox OAuth app, token management, and Dropbox API v2 calls.
|
|
@@ -568,14 +568,154 @@ async function gDriveWriteToFolder(folderId, fileName, content) {
|
|
|
568
568
|
}
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
|
+
// ── OneDrive (Microsoft Graph, path-addressed) ──
|
|
572
|
+
const MS_TOKEN_DIR = path.join(SETTINGS_DIR, "tokens", "microsoft");
|
|
573
|
+
// offline_access is a SCOPE on the Microsoft identity platform (unlike
|
|
574
|
+
// Google's access_type=offline query param) — required for a refresh token.
|
|
575
|
+
const ONEDRIVE_SCOPES = "https://graph.microsoft.com/Files.ReadWrite offline_access";
|
|
576
|
+
const GRAPH_ROOT = "https://graph.microsoft.com/v1.0/me/drive";
|
|
577
|
+
function findMicrosoftCredentials() {
|
|
578
|
+
const local = path.join(SETTINGS_DIR, "microsoft-credentials.json");
|
|
579
|
+
return fs.existsSync(local) ? local : null;
|
|
580
|
+
}
|
|
581
|
+
async function getOneDriveToken() {
|
|
582
|
+
const creds = findMicrosoftCredentials();
|
|
583
|
+
if (!creds) {
|
|
584
|
+
console.error(` [cloud] No Microsoft credentials — expected ${path.join(SETTINGS_DIR, "microsoft-credentials.json")} (Azure portal → App registrations → public/native client)`);
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
const token = await authenticateOAuth(creds, {
|
|
589
|
+
scope: ONEDRIVE_SCOPES,
|
|
590
|
+
tokenDirectory: MS_TOKEN_DIR,
|
|
591
|
+
tokenFileName: "onedrive-token.json",
|
|
592
|
+
credentialsKey: "installed",
|
|
593
|
+
// Google-ism; Microsoft grants refresh tokens via the
|
|
594
|
+
// offline_access scope above instead.
|
|
595
|
+
includeOfflineAccess: false,
|
|
596
|
+
logFn: (type, message) => {
|
|
597
|
+
const p = " [oauth onedrive]";
|
|
598
|
+
if (type === "error")
|
|
599
|
+
console.error(`${p} ${message}`);
|
|
600
|
+
else
|
|
601
|
+
console.log(`${p} ${message}`);
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
if (!token?.access_token)
|
|
605
|
+
console.error(" [cloud] OneDrive auth: returned null — OAuth likely timed out");
|
|
606
|
+
return token?.access_token || null;
|
|
607
|
+
}
|
|
608
|
+
catch (e) {
|
|
609
|
+
console.error(` [cloud] OneDrive auth FAILED: ${e.message}`);
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/** Graph item URL for `<basePath>/<fileName>` under the drive root. */
|
|
614
|
+
function graphContentUrl(basePath, fileName) {
|
|
615
|
+
const rel = [basePath.replace(/^\/+|\/+$/g, ""), fileName]
|
|
616
|
+
.filter(Boolean).join("/")
|
|
617
|
+
.split("/").map(encodeURIComponent).join("/");
|
|
618
|
+
return `${GRAPH_ROOT}/root:/${rel}:/content`;
|
|
619
|
+
}
|
|
620
|
+
/** Create every segment of basePath that doesn't exist yet. Graph's
|
|
621
|
+
* PUT :/content does NOT auto-create parent folders — a fresh account's
|
|
622
|
+
* first write would 404. conflictBehavior:fail + tolerating the
|
|
623
|
+
* nameAlreadyExists error makes this an idempotent walk. Once per
|
|
624
|
+
* process per path — after that the folders exist. */
|
|
625
|
+
const _ensuredPaths = new Set();
|
|
626
|
+
async function oneDriveEnsurePath(token, basePath) {
|
|
627
|
+
const clean = basePath.replace(/^\/+|\/+$/g, "");
|
|
628
|
+
if (!clean || _ensuredPaths.has(clean))
|
|
629
|
+
return;
|
|
630
|
+
let parent = "";
|
|
631
|
+
for (const seg of clean.split("/")) {
|
|
632
|
+
const url = parent
|
|
633
|
+
? `${GRAPH_ROOT}/root:/${parent.split("/").map(encodeURIComponent).join("/")}:/children`
|
|
634
|
+
: `${GRAPH_ROOT}/root/children`;
|
|
635
|
+
const r = await fetch(url, {
|
|
636
|
+
method: "POST",
|
|
637
|
+
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
|
638
|
+
body: JSON.stringify({ name: seg, folder: {}, "@microsoft.graph.conflictBehavior": "fail" }),
|
|
639
|
+
});
|
|
640
|
+
// 201 created, 409 nameAlreadyExists — both mean the segment exists now.
|
|
641
|
+
if (!r.ok && r.status !== 409) {
|
|
642
|
+
throw new Error(`OneDrive folder create "${seg}" failed: ${r.status} ${await r.text().catch(() => "")}`);
|
|
643
|
+
}
|
|
644
|
+
parent = parent ? `${parent}/${seg}` : seg;
|
|
645
|
+
}
|
|
646
|
+
_ensuredPaths.add(clean);
|
|
647
|
+
}
|
|
648
|
+
async function oneDriveRead(basePath, fileName) {
|
|
649
|
+
const token = await getOneDriveToken();
|
|
650
|
+
if (!token)
|
|
651
|
+
return null;
|
|
652
|
+
const r = await fetch(graphContentUrl(basePath, fileName), { headers: { "Authorization": `Bearer ${token}` } });
|
|
653
|
+
if (r.status === 404)
|
|
654
|
+
return null;
|
|
655
|
+
if (!r.ok) {
|
|
656
|
+
console.error(` [cloud] OneDrive read ${fileName}: ${r.status}`);
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
return await r.text();
|
|
660
|
+
}
|
|
661
|
+
async function oneDriveReadBinary(basePath, fileName) {
|
|
662
|
+
const token = await getOneDriveToken();
|
|
663
|
+
if (!token)
|
|
664
|
+
return null;
|
|
665
|
+
const r = await fetch(graphContentUrl(basePath, fileName), { headers: { "Authorization": `Bearer ${token}` } });
|
|
666
|
+
if (r.status === 404)
|
|
667
|
+
return null;
|
|
668
|
+
if (!r.ok) {
|
|
669
|
+
console.error(` [cloud] OneDrive readBinary ${fileName}: ${r.status}`);
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
return Buffer.from(await r.arrayBuffer()).toString("base64");
|
|
673
|
+
}
|
|
674
|
+
async function oneDriveWrite(basePath, fileName, content) {
|
|
675
|
+
const token = await getOneDriveToken();
|
|
676
|
+
if (!token)
|
|
677
|
+
throw new Error("OneDrive auth failed — cannot write");
|
|
678
|
+
const put = () => fetch(graphContentUrl(basePath, fileName), {
|
|
679
|
+
method: "PUT",
|
|
680
|
+
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "text/plain" },
|
|
681
|
+
body: content,
|
|
682
|
+
});
|
|
683
|
+
let r = await put();
|
|
684
|
+
if (r.status === 404 || r.status === 409) {
|
|
685
|
+
// Parent path missing (fresh account) — create it and retry once.
|
|
686
|
+
await oneDriveEnsurePath(token, basePath);
|
|
687
|
+
r = await put();
|
|
688
|
+
}
|
|
689
|
+
if (!r.ok)
|
|
690
|
+
throw new Error(`OneDrive write ${fileName} failed: ${r.status} ${await r.text().catch(() => "")}`);
|
|
691
|
+
}
|
|
692
|
+
/** Fetch the authenticated Microsoft user's profile (setup-flow display). */
|
|
693
|
+
export async function getMicrosoftProfile() {
|
|
694
|
+
const token = await getOneDriveToken();
|
|
695
|
+
if (!token)
|
|
696
|
+
return null;
|
|
697
|
+
try {
|
|
698
|
+
const r = await fetch("https://graph.microsoft.com/v1.0/me", { headers: { "Authorization": `Bearer ${token}` } });
|
|
699
|
+
if (!r.ok)
|
|
700
|
+
return null;
|
|
701
|
+
const me = await r.json();
|
|
702
|
+
return { name: me.displayName, email: me.mail || me.userPrincipalName };
|
|
703
|
+
}
|
|
704
|
+
catch {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
571
708
|
/**
|
|
572
|
-
* Get a cloud file provider.
|
|
573
|
-
*
|
|
709
|
+
* Get a cloud file provider. The locator is provider-specific: gdrive takes
|
|
710
|
+
* the folder ID; onedrive takes the base PATH under the drive root (e.g.
|
|
711
|
+
* "home/.rmfmail"). Files are stored flat in that folder either way — the
|
|
712
|
+
* CloudFile contract is identical, only the FS API underneath differs.
|
|
574
713
|
*/
|
|
575
|
-
export function getCloudProvider(provider,
|
|
714
|
+
export function getCloudProvider(provider, locator) {
|
|
576
715
|
switch (provider) {
|
|
577
716
|
case "google":
|
|
578
|
-
case "gdrive":
|
|
717
|
+
case "gdrive": {
|
|
718
|
+
const folderId = locator;
|
|
579
719
|
if (!folderId) {
|
|
580
720
|
console.error(" [cloud] gdrive requires a folder ID — run initCloudConfig first");
|
|
581
721
|
return null;
|
|
@@ -586,6 +726,16 @@ export function getCloudProvider(provider, folderId) {
|
|
|
586
726
|
write: (fileName, content) => gDriveWriteToFolder(folderId, fileName, content),
|
|
587
727
|
exists: async (fileName) => (await gDriveReadFromFolder(folderId, fileName)) !== null,
|
|
588
728
|
};
|
|
729
|
+
}
|
|
730
|
+
case "onedrive": {
|
|
731
|
+
const basePath = locator || "home/.rmfmail";
|
|
732
|
+
return {
|
|
733
|
+
read: (fileName) => oneDriveRead(basePath, fileName),
|
|
734
|
+
readBinary: (fileName) => oneDriveReadBinary(basePath, fileName),
|
|
735
|
+
write: (fileName, content) => oneDriveWrite(basePath, fileName, content),
|
|
736
|
+
exists: async (fileName) => (await oneDriveRead(basePath, fileName)) !== null,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
589
739
|
case "local":
|
|
590
740
|
return {
|
|
591
741
|
read: async (p) => { try {
|
|
@@ -604,7 +754,7 @@ export function getCloudProvider(provider, folderId) {
|
|
|
604
754
|
exists: async (p) => fs.existsSync(p),
|
|
605
755
|
};
|
|
606
756
|
default:
|
|
607
|
-
console.error(` [cloud] Provider "${provider}" not supported —
|
|
757
|
+
console.error(` [cloud] Provider "${provider}" not supported — gdrive and onedrive are available`);
|
|
608
758
|
return null;
|
|
609
759
|
}
|
|
610
760
|
}
|
package/docs/preferences.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"ui": {
|
|
14
14
|
"theme": "system", // "system" | "light" | "dark"
|
|
15
15
|
"twoLine": false, // two-line message-list rows
|
|
16
|
+
"eleanorView": false, // one-line ★|Subject|From|Date list (Thunderbird-classic)
|
|
16
17
|
"previewPane": true,
|
|
17
18
|
"previewSnippets": true,
|
|
18
19
|
"threaded": false,
|
package/index.d.ts
CHANGED
|
@@ -231,7 +231,7 @@ export declare function initLocalConfig(sharedDir?: string, storePath?: string):
|
|
|
231
231
|
* Finds or creates the app-owned ".rmfmail" folder via Drive API and stores its ID.
|
|
232
232
|
* No mount scanning — API only. Existing settings at other paths (e.g., home/.rmfmail
|
|
233
233
|
* from Desktop sync) must be migrated manually or via config.jsonc importPath. */
|
|
234
|
-
export declare function initCloudConfig(provider?: "gdrive"): Promise<void>;
|
|
234
|
+
export declare function initCloudConfig(provider?: "gdrive" | "onedrive"): Promise<void>;
|
|
235
235
|
declare const DEFAULT_SETTINGS: MailxSettings;
|
|
236
236
|
/** Get historyDays for an account: per-account override > system override > shared default */
|
|
237
237
|
export declare function getHistoryDays(accountId?: string): number;
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAyG5G,QAAA,MAAM,SAAS,QAA4E,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -146,6 +146,12 @@ const LOCAL_DIR = path.join(process.env.USERPROFILE || process.env.HOME || ".",
|
|
|
146
146
|
const LOCAL_CONFIG_PATH = path.join(LOCAL_DIR, "config.jsonc");
|
|
147
147
|
const LEGACY_CONFIG_PATH = path.join(LOCAL_DIR, "config.json");
|
|
148
148
|
const DEFAULT_STORE_PATH = path.join(LOCAL_DIR, "mailxstore");
|
|
149
|
+
/** Provider-specific locator for getCloudProvider: gdrive wants the cached
|
|
150
|
+
* folder ID, onedrive wants the base path. One helper so the call sites
|
|
151
|
+
* can't drift apart. */
|
|
152
|
+
function cloudLocator(cfg) {
|
|
153
|
+
return cfg.provider === "onedrive" ? cfg.path : cfg.folderId;
|
|
154
|
+
}
|
|
149
155
|
/** Resolve a path from config — relative to ~/.mailx/, ~ expands to home */
|
|
150
156
|
function resolvePath(p) {
|
|
151
157
|
if (!p)
|
|
@@ -174,7 +180,7 @@ function readLocalConfig() {
|
|
|
174
180
|
/** Resolve provider config to a filesystem path (checks for local Google Drive mount) */
|
|
175
181
|
function resolveProvider(cfg) {
|
|
176
182
|
// Cloud providers use API only — no filesystem mount scanning
|
|
177
|
-
if (cfg.provider === "gdrive" || cfg.provider === "google")
|
|
183
|
+
if (cfg.provider === "gdrive" || cfg.provider === "google" || cfg.provider === "onedrive")
|
|
178
184
|
return undefined;
|
|
179
185
|
if (cfg.provider === "local")
|
|
180
186
|
return resolvePath(cfg.path);
|
|
@@ -248,8 +254,8 @@ export async function cloudRead(filename) {
|
|
|
248
254
|
clearFolderIdInConfig();
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
|
-
// Ensure we have a folder ID
|
|
252
|
-
if (!pendingCloudConfig.folderId) {
|
|
257
|
+
// Ensure we have a folder ID (gdrive only — onedrive is path-addressed)
|
|
258
|
+
if (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId) {
|
|
253
259
|
pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
|
|
254
260
|
if (pendingCloudConfig.folderId)
|
|
255
261
|
saveFolderIdToConfig(pendingCloudConfig.folderId);
|
|
@@ -258,7 +264,7 @@ export async function cloudRead(filename) {
|
|
|
258
264
|
return null;
|
|
259
265
|
}
|
|
260
266
|
}
|
|
261
|
-
const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig
|
|
267
|
+
const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
|
|
262
268
|
if (!provider) {
|
|
263
269
|
setCloudError(`No cloud provider for ${pendingCloudConfig.provider}`, { op: "read", filename });
|
|
264
270
|
return null;
|
|
@@ -296,14 +302,14 @@ export async function cloudRead(filename) {
|
|
|
296
302
|
export async function cloudReadBinary(filename) {
|
|
297
303
|
if (!pendingCloudConfig)
|
|
298
304
|
return null;
|
|
299
|
-
if (!pendingCloudConfig.folderId) {
|
|
305
|
+
if (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId) {
|
|
300
306
|
pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
|
|
301
307
|
if (pendingCloudConfig.folderId)
|
|
302
308
|
saveFolderIdToConfig(pendingCloudConfig.folderId);
|
|
303
309
|
if (!pendingCloudConfig.folderId)
|
|
304
310
|
return null;
|
|
305
311
|
}
|
|
306
|
-
const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig
|
|
312
|
+
const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
|
|
307
313
|
if (!provider?.readBinary)
|
|
308
314
|
return null;
|
|
309
315
|
try {
|
|
@@ -327,8 +333,8 @@ export async function cloudWrite(filename, content) {
|
|
|
327
333
|
setCloudError(err, { op: "write", filename });
|
|
328
334
|
throw new Error(err);
|
|
329
335
|
}
|
|
330
|
-
// Ensure we have a folder ID
|
|
331
|
-
if (!pendingCloudConfig.folderId) {
|
|
336
|
+
// Ensure we have a folder ID (gdrive only — onedrive is path-addressed)
|
|
337
|
+
if (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId) {
|
|
332
338
|
pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
|
|
333
339
|
if (pendingCloudConfig.folderId)
|
|
334
340
|
saveFolderIdToConfig(pendingCloudConfig.folderId);
|
|
@@ -338,7 +344,7 @@ export async function cloudWrite(filename, content) {
|
|
|
338
344
|
throw new Error(err);
|
|
339
345
|
}
|
|
340
346
|
}
|
|
341
|
-
const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig
|
|
347
|
+
const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
|
|
342
348
|
if (!provider) {
|
|
343
349
|
const err = `No cloud provider for ${pendingCloudConfig.provider}`;
|
|
344
350
|
setCloudError(err, { op: "write", filename });
|
|
@@ -403,8 +409,8 @@ export function getStorageInfo() {
|
|
|
403
409
|
}
|
|
404
410
|
// Provider-based entry — check for API mode (folderId) first, then mount
|
|
405
411
|
const name = (entry.provider === "gdrive" || entry.provider === "google") ? "gdrive" : entry.provider;
|
|
406
|
-
if (entry.folderId) {
|
|
407
|
-
// Has folder ID → API mode (don't scan filesystem for mounts)
|
|
412
|
+
if (entry.folderId || entry.provider === "onedrive") {
|
|
413
|
+
// Has folder ID (or is path-addressed onedrive) → API mode (don't scan filesystem for mounts)
|
|
408
414
|
return { provider: name, mode: "api", cloudPath: entry.path, folderName: entry.path, folderId: entry.folderId, configDir, cloudError: lastCloudError || undefined };
|
|
409
415
|
}
|
|
410
416
|
const resolved = resolveSharedEntry(entry);
|
|
@@ -1385,17 +1391,25 @@ export async function initCloudConfig(provider = "gdrive") {
|
|
|
1385
1391
|
const existing = readLocalConfig();
|
|
1386
1392
|
if (existing.sharedDir)
|
|
1387
1393
|
return; // Already configured
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
+
let sharedDir;
|
|
1395
|
+
if (provider === "onedrive") {
|
|
1396
|
+
// Path-addressed — no folder discovery; the first write creates the
|
|
1397
|
+
// folder chain (oneDriveEnsurePath). Same layout convention as Drive.
|
|
1398
|
+
sharedDir = { provider, path: "home/.rmfmail" };
|
|
1399
|
+
}
|
|
1400
|
+
else {
|
|
1401
|
+
// Find or create the settings folder via Drive API. Stored path is the
|
|
1402
|
+
// canonical post-rebrand location; the folderId is what actually drives
|
|
1403
|
+
// subsequent reads/writes, so even legacy users with the old folder name
|
|
1404
|
+
// work fine since gDriveFindOrCreateFolder resolves whichever exists.
|
|
1405
|
+
const folderId = await gDriveFindOrCreateFolder();
|
|
1406
|
+
sharedDir = { provider, path: "home/.rmfmail", folderId: folderId || undefined };
|
|
1407
|
+
}
|
|
1394
1408
|
const config = { ...existing, sharedDir, storePath: existing.storePath || DEFAULT_STORE_PATH };
|
|
1395
1409
|
fs.mkdirSync(LOCAL_DIR, { recursive: true });
|
|
1396
1410
|
atomicWrite(LOCAL_CONFIG_PATH, config);
|
|
1397
1411
|
pendingCloudConfig = sharedDir;
|
|
1398
|
-
console.log(` Initialized cloud config: ${provider} (folder ID: ${folderId
|
|
1412
|
+
console.log(` Initialized cloud config: ${provider} (${sharedDir.folderId ? `folder ID: ${sharedDir.folderId}` : sharedDir.path})`);
|
|
1399
1413
|
}
|
|
1400
1414
|
const DEFAULT_SETTINGS = {
|
|
1401
1415
|
accounts: [],
|
|
@@ -1437,9 +1451,9 @@ export { DEFAULT_SETTINGS, DEFAULT_ALLOWLIST, DEFAULT_PREFERENCES, DEFAULT_AUTOC
|
|
|
1437
1451
|
* bearing — the app works without them.
|
|
1438
1452
|
*/
|
|
1439
1453
|
export async function deployDocs(appVersion) {
|
|
1440
|
-
if (!pendingCloudConfig
|
|
1454
|
+
if (!pendingCloudConfig || (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId))
|
|
1441
1455
|
return;
|
|
1442
|
-
const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig
|
|
1456
|
+
const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
|
|
1443
1457
|
if (!provider)
|
|
1444
1458
|
return;
|
|
1445
1459
|
// Resolve the source docs dir. In dev the workspace has `app/docs/`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-settings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
20
|
+
"@bobfrankston/mailx-types": "^0.1.24",
|
|
21
21
|
"jsonc-parser": "^3.3.1"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
".transformedSnapshot": {
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
36
|
+
"@bobfrankston/mailx-types": "^0.1.24",
|
|
37
37
|
"jsonc-parser": "^3.3.1"
|
|
38
38
|
}
|
|
39
39
|
}
|