@bobfrankston/mailx 1.0.178 → 1.0.180
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/client/android.html +156 -0
- package/client/components/message-viewer.js +5 -3
- package/client/lib/android-bootstrap.js +9 -0
- package/client/lib/api-client.js +83 -75
- package/package.json +6 -4
- package/packages/mailx-imap/index.js +24 -16
- package/packages/mailx-imap/providers/gmail-api.js +4 -4
- package/packages/mailx-store-web/android-bootstrap.d.ts +16 -0
- package/packages/mailx-store-web/android-bootstrap.js +340 -0
- package/packages/mailx-store-web/db.d.ts +112 -0
- package/packages/mailx-store-web/db.js +508 -0
- package/packages/mailx-store-web/gmail-api-web.d.ts +28 -0
- package/packages/mailx-store-web/gmail-api-web.js +231 -0
- package/packages/mailx-store-web/index.d.ts +10 -0
- package/packages/mailx-store-web/index.js +10 -0
- package/packages/mailx-store-web/package.json +19 -0
- package/packages/mailx-store-web/provider-types.d.ts +50 -0
- package/packages/mailx-store-web/provider-types.js +7 -0
- package/packages/mailx-store-web/sql.js.d.ts +29 -0
- package/packages/mailx-store-web/web-jsonrpc.d.ts +20 -0
- package/packages/mailx-store-web/web-jsonrpc.js +94 -0
- package/packages/mailx-store-web/web-message-store.d.ts +16 -0
- package/packages/mailx-store-web/web-message-store.js +89 -0
- package/packages/mailx-store-web/web-service.d.ts +92 -0
- package/packages/mailx-store-web/web-service.js +481 -0
- package/packages/mailx-store-web/web-settings.d.ts +81 -0
- package/packages/mailx-store-web/web-settings.js +421 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible settings for Android/WebView.
|
|
3
|
+
* Replaces @bobfrankston/mailx-settings which depends on node:fs.
|
|
4
|
+
*
|
|
5
|
+
* Settings are stored in IndexedDB and synced to Google Drive
|
|
6
|
+
* via the GDrive API (same API as desktop cloud mode).
|
|
7
|
+
*
|
|
8
|
+
* On first run, settings are fetched from GDrive. Subsequent reads
|
|
9
|
+
* use the local IndexedDB cache. Writes go to both.
|
|
10
|
+
*/
|
|
11
|
+
const IDB_NAME = "mailx-settings";
|
|
12
|
+
const IDB_VERSION = 1;
|
|
13
|
+
const STORE_NAME = "files";
|
|
14
|
+
// ── IndexedDB helpers ──
|
|
15
|
+
function openSettingsDb() {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const req = indexedDB.open(IDB_NAME, IDB_VERSION);
|
|
18
|
+
req.onupgradeneeded = () => {
|
|
19
|
+
const db = req.result;
|
|
20
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
21
|
+
db.createObjectStore(STORE_NAME);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
req.onsuccess = () => resolve(req.result);
|
|
25
|
+
req.onerror = () => reject(req.error);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async function idbRead(key) {
|
|
29
|
+
const db = await openSettingsDb();
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
32
|
+
const req = tx.objectStore(STORE_NAME).get(key);
|
|
33
|
+
req.onsuccess = () => resolve(req.result);
|
|
34
|
+
req.onerror = () => reject(req.error);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function idbWrite(key, value) {
|
|
38
|
+
const db = await openSettingsDb();
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
41
|
+
tx.objectStore(STORE_NAME).put(value, key);
|
|
42
|
+
tx.oncomplete = () => resolve();
|
|
43
|
+
tx.onerror = () => reject(tx.error);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function idbDelete(key) {
|
|
47
|
+
const db = await openSettingsDb();
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
50
|
+
tx.objectStore(STORE_NAME).delete(key);
|
|
51
|
+
tx.oncomplete = () => resolve();
|
|
52
|
+
tx.onerror = () => reject(tx.error);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// ── GDrive API ──
|
|
56
|
+
/** GDrive folder ID for the "mailx" app folder */
|
|
57
|
+
let gDriveFolderId = null;
|
|
58
|
+
/** OAuth token provider — set by bootstrap */
|
|
59
|
+
let tokenProvider = null;
|
|
60
|
+
export function setGDriveTokenProvider(provider) {
|
|
61
|
+
tokenProvider = provider;
|
|
62
|
+
}
|
|
63
|
+
export function setGDriveFolderId(folderId) {
|
|
64
|
+
gDriveFolderId = folderId;
|
|
65
|
+
}
|
|
66
|
+
async function gDriveRead(filename) {
|
|
67
|
+
if (!tokenProvider || !gDriveFolderId)
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
const token = await tokenProvider();
|
|
71
|
+
// Find file by name in folder
|
|
72
|
+
const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
|
|
73
|
+
const listRes = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id)`, { headers: { "Authorization": `Bearer ${token}` } });
|
|
74
|
+
if (!listRes.ok)
|
|
75
|
+
return null;
|
|
76
|
+
const listData = await listRes.json();
|
|
77
|
+
const fileId = listData.files?.[0]?.id;
|
|
78
|
+
if (!fileId)
|
|
79
|
+
return null;
|
|
80
|
+
// Download content
|
|
81
|
+
const res = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, { headers: { "Authorization": `Bearer ${token}` } });
|
|
82
|
+
if (!res.ok)
|
|
83
|
+
return null;
|
|
84
|
+
return res.text();
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
console.error(`[settings] GDrive read ${filename}: ${e.message}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function gDriveWrite(filename, content) {
|
|
92
|
+
if (!tokenProvider || !gDriveFolderId)
|
|
93
|
+
return false;
|
|
94
|
+
try {
|
|
95
|
+
const token = await tokenProvider();
|
|
96
|
+
// Check if file exists
|
|
97
|
+
const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
|
|
98
|
+
const listRes = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id)`, { headers: { "Authorization": `Bearer ${token}` } });
|
|
99
|
+
if (!listRes.ok)
|
|
100
|
+
return false;
|
|
101
|
+
const listData = await listRes.json();
|
|
102
|
+
const fileId = listData.files?.[0]?.id;
|
|
103
|
+
if (fileId) {
|
|
104
|
+
// Update existing
|
|
105
|
+
const res = await globalThis.fetch(`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`, {
|
|
106
|
+
method: "PATCH",
|
|
107
|
+
headers: {
|
|
108
|
+
"Authorization": `Bearer ${token}`,
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
},
|
|
111
|
+
body: content,
|
|
112
|
+
});
|
|
113
|
+
return res.ok;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Create new
|
|
117
|
+
const metadata = JSON.stringify({
|
|
118
|
+
name: filename,
|
|
119
|
+
parents: [gDriveFolderId],
|
|
120
|
+
mimeType: "application/json",
|
|
121
|
+
});
|
|
122
|
+
const boundary = "----mailx" + Date.now();
|
|
123
|
+
const body = `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${metadata}\r\n--${boundary}\r\nContent-Type: application/json\r\n\r\n${content}\r\n--${boundary}--`;
|
|
124
|
+
const res = await globalThis.fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"Authorization": `Bearer ${token}`,
|
|
128
|
+
"Content-Type": `multipart/related; boundary=${boundary}`,
|
|
129
|
+
},
|
|
130
|
+
body,
|
|
131
|
+
});
|
|
132
|
+
return res.ok;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
console.error(`[settings] GDrive write ${filename}: ${e.message}`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const PROVIDERS = {
|
|
141
|
+
"gmail.com": {
|
|
142
|
+
label: "Gmail",
|
|
143
|
+
imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
|
|
144
|
+
smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
|
|
145
|
+
},
|
|
146
|
+
"googlemail.com": {
|
|
147
|
+
label: "Gmail",
|
|
148
|
+
imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
|
|
149
|
+
smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
|
|
150
|
+
},
|
|
151
|
+
"outlook.com": {
|
|
152
|
+
label: "Outlook",
|
|
153
|
+
imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
|
|
154
|
+
smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
|
|
155
|
+
},
|
|
156
|
+
"hotmail.com": {
|
|
157
|
+
label: "Hotmail",
|
|
158
|
+
imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
|
|
159
|
+
smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
|
|
160
|
+
},
|
|
161
|
+
"yahoo.com": {
|
|
162
|
+
label: "Yahoo",
|
|
163
|
+
imap: { host: "imap.mail.yahoo.com", port: 993, tls: true, auth: "password" },
|
|
164
|
+
smtp: { host: "smtp.mail.yahoo.com", port: 587, tls: true, auth: "password" },
|
|
165
|
+
},
|
|
166
|
+
"icloud.com": {
|
|
167
|
+
label: "iCloud",
|
|
168
|
+
imap: { host: "imap.mail.me.com", port: 993, tls: true, auth: "password" },
|
|
169
|
+
smtp: { host: "smtp.mail.me.com", port: 587, tls: true, auth: "password" },
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
function normalizeAccount(acct, globalName) {
|
|
173
|
+
const email = acct.email || "";
|
|
174
|
+
const domain = email.split("@")[1]?.toLowerCase() || "";
|
|
175
|
+
const provider = PROVIDERS[domain];
|
|
176
|
+
const user = acct.imap?.user || acct.user || email;
|
|
177
|
+
return {
|
|
178
|
+
id: acct.id || domain.split(".")[0] || "account",
|
|
179
|
+
name: acct.name || globalName || email.split("@")[0],
|
|
180
|
+
label: acct.label || provider?.label,
|
|
181
|
+
email,
|
|
182
|
+
imap: {
|
|
183
|
+
host: acct.imap?.host || provider?.imap.host || `imap.${domain}`,
|
|
184
|
+
port: acct.imap?.port || provider?.imap.port || 993,
|
|
185
|
+
tls: acct.imap?.tls ?? provider?.imap.tls ?? true,
|
|
186
|
+
auth: acct.imap?.auth || provider?.imap.auth || "password",
|
|
187
|
+
user: acct.imap?.user || user,
|
|
188
|
+
password: acct.imap?.password || acct.password,
|
|
189
|
+
},
|
|
190
|
+
smtp: {
|
|
191
|
+
host: acct.smtp?.host || provider?.smtp.host || `smtp.${domain}`,
|
|
192
|
+
port: acct.smtp?.port || provider?.smtp.port || 587,
|
|
193
|
+
tls: acct.smtp?.tls ?? provider?.smtp.tls ?? true,
|
|
194
|
+
auth: acct.smtp?.auth || provider?.smtp.auth || "password",
|
|
195
|
+
user: acct.smtp?.user || user,
|
|
196
|
+
password: acct.smtp?.password || acct.password,
|
|
197
|
+
},
|
|
198
|
+
enabled: acct.enabled ?? true,
|
|
199
|
+
defaultSend: acct.defaultSend,
|
|
200
|
+
syncContacts: acct.syncContacts ?? (provider?.imap.auth === "oauth2"),
|
|
201
|
+
relayDomains: acct.relayDomains,
|
|
202
|
+
deliveredToPrefix: acct.deliveredToPrefix,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// ── Default settings ──
|
|
206
|
+
const DEFAULT_PREFERENCES = {
|
|
207
|
+
ui: {
|
|
208
|
+
theme: "system",
|
|
209
|
+
editor: "quill",
|
|
210
|
+
folderWidth: 220,
|
|
211
|
+
listViewerSplit: 40,
|
|
212
|
+
fontSize: 15,
|
|
213
|
+
},
|
|
214
|
+
sync: {
|
|
215
|
+
intervalMinutes: 5,
|
|
216
|
+
historyDays: 30,
|
|
217
|
+
prefetch: true,
|
|
218
|
+
},
|
|
219
|
+
autocomplete: {
|
|
220
|
+
enabled: false,
|
|
221
|
+
provider: "off",
|
|
222
|
+
ollamaUrl: "",
|
|
223
|
+
ollamaModel: "",
|
|
224
|
+
cloudApiKey: "",
|
|
225
|
+
cloudModel: "",
|
|
226
|
+
debounceMs: 600,
|
|
227
|
+
maxTokens: 60,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
const DEFAULT_ALLOWLIST = {
|
|
231
|
+
senders: [],
|
|
232
|
+
domains: [],
|
|
233
|
+
recipients: [],
|
|
234
|
+
};
|
|
235
|
+
// ── Public API ──
|
|
236
|
+
/** Load accounts — first from IndexedDB cache, then GDrive */
|
|
237
|
+
export async function loadAccounts() {
|
|
238
|
+
// Try local cache first
|
|
239
|
+
const cached = await idbRead("accounts.jsonc");
|
|
240
|
+
if (cached) {
|
|
241
|
+
try {
|
|
242
|
+
const data = JSON.parse(cached);
|
|
243
|
+
const raw = data.accounts || (Array.isArray(data) ? data : []);
|
|
244
|
+
if (raw.length > 0) {
|
|
245
|
+
return raw.map((a) => normalizeAccount(a, data.name));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch { /* parse error — fall through to GDrive */ }
|
|
249
|
+
}
|
|
250
|
+
// Try GDrive
|
|
251
|
+
const content = await gDriveRead("accounts.jsonc");
|
|
252
|
+
if (content) {
|
|
253
|
+
await idbWrite("accounts.jsonc", content);
|
|
254
|
+
try {
|
|
255
|
+
const data = JSON.parse(content);
|
|
256
|
+
const raw = data.accounts || (Array.isArray(data) ? data : []);
|
|
257
|
+
return raw.map((a) => normalizeAccount(a, data.name));
|
|
258
|
+
}
|
|
259
|
+
catch { /* parse error */ }
|
|
260
|
+
}
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
/** Save accounts to IndexedDB and GDrive */
|
|
264
|
+
export async function saveAccounts(accounts) {
|
|
265
|
+
const content = JSON.stringify({ accounts }, null, 2);
|
|
266
|
+
await idbWrite("accounts.jsonc", content);
|
|
267
|
+
await gDriveWrite("accounts.jsonc", content);
|
|
268
|
+
}
|
|
269
|
+
/** Load preferences */
|
|
270
|
+
export async function loadPreferences() {
|
|
271
|
+
const cached = await idbRead("preferences.jsonc");
|
|
272
|
+
if (cached) {
|
|
273
|
+
try {
|
|
274
|
+
const data = JSON.parse(cached);
|
|
275
|
+
return {
|
|
276
|
+
ui: { ...DEFAULT_PREFERENCES.ui, ...data.ui },
|
|
277
|
+
sync: { ...DEFAULT_PREFERENCES.sync, ...data.sync },
|
|
278
|
+
autocomplete: { ...DEFAULT_PREFERENCES.autocomplete, ...data.autocomplete },
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
catch { /* parse error */ }
|
|
282
|
+
}
|
|
283
|
+
// Try GDrive
|
|
284
|
+
const content = await gDriveRead("preferences.jsonc");
|
|
285
|
+
if (content) {
|
|
286
|
+
await idbWrite("preferences.jsonc", content);
|
|
287
|
+
try {
|
|
288
|
+
const data = JSON.parse(content);
|
|
289
|
+
return {
|
|
290
|
+
ui: { ...DEFAULT_PREFERENCES.ui, ...data.ui },
|
|
291
|
+
sync: { ...DEFAULT_PREFERENCES.sync, ...data.sync },
|
|
292
|
+
autocomplete: { ...DEFAULT_PREFERENCES.autocomplete, ...data.autocomplete },
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
catch { /* parse error */ }
|
|
296
|
+
}
|
|
297
|
+
return { ...DEFAULT_PREFERENCES };
|
|
298
|
+
}
|
|
299
|
+
/** Save preferences */
|
|
300
|
+
export async function savePreferences(prefs) {
|
|
301
|
+
const content = JSON.stringify(prefs, null, 2);
|
|
302
|
+
await idbWrite("preferences.jsonc", content);
|
|
303
|
+
await gDriveWrite("preferences.jsonc", content);
|
|
304
|
+
}
|
|
305
|
+
/** Load full settings (accounts + preferences combined) */
|
|
306
|
+
export async function loadSettings() {
|
|
307
|
+
const accounts = await loadAccounts();
|
|
308
|
+
const prefs = await loadPreferences();
|
|
309
|
+
return {
|
|
310
|
+
accounts,
|
|
311
|
+
ui: prefs.ui,
|
|
312
|
+
sync: prefs.sync,
|
|
313
|
+
autocomplete: prefs.autocomplete,
|
|
314
|
+
store: {
|
|
315
|
+
basePath: "indexeddb",
|
|
316
|
+
compressionBoundaryDays: 365,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/** Save full settings */
|
|
321
|
+
export async function saveSettings(settings) {
|
|
322
|
+
await saveAccounts(settings.accounts);
|
|
323
|
+
await savePreferences({ ui: settings.ui, sync: settings.sync, autocomplete: settings.autocomplete });
|
|
324
|
+
}
|
|
325
|
+
/** Load allowlist */
|
|
326
|
+
export async function loadAllowlist() {
|
|
327
|
+
const cached = await idbRead("allowlist.jsonc");
|
|
328
|
+
if (cached) {
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(cached);
|
|
331
|
+
}
|
|
332
|
+
catch { /* */ }
|
|
333
|
+
}
|
|
334
|
+
const content = await gDriveRead("allowlist.jsonc");
|
|
335
|
+
if (content) {
|
|
336
|
+
await idbWrite("allowlist.jsonc", content);
|
|
337
|
+
try {
|
|
338
|
+
return JSON.parse(content);
|
|
339
|
+
}
|
|
340
|
+
catch { /* */ }
|
|
341
|
+
}
|
|
342
|
+
return { ...DEFAULT_ALLOWLIST };
|
|
343
|
+
}
|
|
344
|
+
/** Save allowlist */
|
|
345
|
+
export async function saveAllowlist(list) {
|
|
346
|
+
const content = JSON.stringify(list, null, 2);
|
|
347
|
+
await idbWrite("allowlist.jsonc", content);
|
|
348
|
+
await gDriveWrite("allowlist.jsonc", content);
|
|
349
|
+
}
|
|
350
|
+
/** Load autocomplete settings */
|
|
351
|
+
export async function loadAutocomplete() {
|
|
352
|
+
const prefs = await loadPreferences();
|
|
353
|
+
return prefs.autocomplete;
|
|
354
|
+
}
|
|
355
|
+
/** Save autocomplete settings */
|
|
356
|
+
export async function saveAutocomplete(settings) {
|
|
357
|
+
const prefs = await loadPreferences();
|
|
358
|
+
prefs.autocomplete = settings;
|
|
359
|
+
await savePreferences(prefs);
|
|
360
|
+
}
|
|
361
|
+
/** Get history days — read from preferences */
|
|
362
|
+
export async function getHistoryDays() {
|
|
363
|
+
const prefs = await loadPreferences();
|
|
364
|
+
return prefs.sync.historyDays || 30;
|
|
365
|
+
}
|
|
366
|
+
/** Get prefetch setting */
|
|
367
|
+
export async function getPrefetch() {
|
|
368
|
+
const prefs = await loadPreferences();
|
|
369
|
+
return prefs.sync.prefetch !== false;
|
|
370
|
+
}
|
|
371
|
+
/** Get storage info */
|
|
372
|
+
export function getStorageInfo() {
|
|
373
|
+
return { provider: gDriveFolderId ? "gdrive" : "local", mode: gDriveFolderId ? "api" : "local" };
|
|
374
|
+
}
|
|
375
|
+
/** Clear all cached settings — used for "Reset Store" */
|
|
376
|
+
export async function clearSettings() {
|
|
377
|
+
await idbDelete("accounts.jsonc");
|
|
378
|
+
await idbDelete("preferences.jsonc");
|
|
379
|
+
await idbDelete("allowlist.jsonc");
|
|
380
|
+
}
|
|
381
|
+
// ── Per-device settings ──
|
|
382
|
+
const DEVICE_ID_KEY = "mailx-device-id";
|
|
383
|
+
/** Get or create a stable device ID (UUID stored in localStorage) */
|
|
384
|
+
export function getDeviceId() {
|
|
385
|
+
let id = localStorage.getItem(DEVICE_ID_KEY);
|
|
386
|
+
if (!id) {
|
|
387
|
+
id = crypto.randomUUID();
|
|
388
|
+
localStorage.setItem(DEVICE_ID_KEY, id);
|
|
389
|
+
}
|
|
390
|
+
return id;
|
|
391
|
+
}
|
|
392
|
+
/** Save device-specific settings to GDrive (devices/<deviceId>/state.json) */
|
|
393
|
+
export async function saveDeviceState(state) {
|
|
394
|
+
const deviceId = getDeviceId();
|
|
395
|
+
const filename = `devices/${deviceId}/state.json`;
|
|
396
|
+
const content = JSON.stringify(state, null, 2);
|
|
397
|
+
await idbWrite(filename, content);
|
|
398
|
+
await gDriveWrite(filename, content);
|
|
399
|
+
}
|
|
400
|
+
/** Load device-specific settings */
|
|
401
|
+
export async function loadDeviceState() {
|
|
402
|
+
const deviceId = getDeviceId();
|
|
403
|
+
const filename = `devices/${deviceId}/state.json`;
|
|
404
|
+
const cached = await idbRead(filename);
|
|
405
|
+
if (cached) {
|
|
406
|
+
try {
|
|
407
|
+
return JSON.parse(cached);
|
|
408
|
+
}
|
|
409
|
+
catch { /* */ }
|
|
410
|
+
}
|
|
411
|
+
const content = await gDriveRead(filename);
|
|
412
|
+
if (content) {
|
|
413
|
+
await idbWrite(filename, content);
|
|
414
|
+
try {
|
|
415
|
+
return JSON.parse(content);
|
|
416
|
+
}
|
|
417
|
+
catch { /* */ }
|
|
418
|
+
}
|
|
419
|
+
return {};
|
|
420
|
+
}
|
|
421
|
+
//# sourceMappingURL=web-settings.js.map
|