nx-webmail: add robust paged inbox hydration after login
Some checks failed
Publish nx-webmail Image (Gitea) / publish (push) Has been cancelled
Some checks failed
Publish nx-webmail Image (Gitea) / publish (push) Has been cancelled
This commit is contained in:
@@ -30,7 +30,7 @@ Umbrel installation is most reliable when your app uses a prebuilt image from a
|
|||||||
- `git.weektab.org/nexus/nx-webmail:latest`
|
- `git.weektab.org/nexus/nx-webmail:latest`
|
||||||
3. The workflow then pins `nx-webmail/docker-compose.yml` to `tag@sha256:digest` automatically.
|
3. The workflow then pins `nx-webmail/docker-compose.yml` to `tag@sha256:digest` automatically.
|
||||||
4. Manual fallback:
|
4. Manual fallback:
|
||||||
docker buildx build --platform linux/amd64,linux/arm64 -t git.weektab.org/nexus/nx-webmail:1.0.7 --push .
|
docker buildx build --platform linux/amd64,linux/arm64 -t git.weektab.org/nexus/nx-webmail:1.0.8 --push .
|
||||||
|
|
||||||
## Umbrel app packaging
|
## Umbrel app packaging
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ This repository is prepared for Umbrel app-store usage.
|
|||||||
Notes:
|
Notes:
|
||||||
- Umbrel uses the `app_proxy` service in `docker-compose.yml`.
|
- Umbrel uses the `app_proxy` service in `docker-compose.yml`.
|
||||||
- Internal app port is `3001`.
|
- Internal app port is `3001`.
|
||||||
- `server` uses a prebuilt registry image (`git.weektab.org/nexus/nx-webmail:1.0.7`).
|
- `server` uses a prebuilt registry image (`git.weektab.org/nexus/nx-webmail:1.0.8`).
|
||||||
- If your store prefix changes, update `id` in `umbrel-app.yml` and `APP_HOST` in `docker-compose.yml`.
|
- If your store prefix changes, update `id` in `umbrel-app.yml` and `APP_HOST` in `docker-compose.yml`.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
APP_PORT: 3001
|
APP_PORT: 3001
|
||||||
|
|
||||||
server:
|
server:
|
||||||
image: git.weektab.org/nexus/nx-webmail:1.0.7@sha256:01dfba4f671f490f3dddf9b5ddc3a98bfff7f10e8a87aa47b4873f8ac1a3d332
|
image: git.weektab.org/nexus/nx-webmail:1.0.8@sha256:db773eea0c6d836360ad52a7e55f25d5f1b3684a73e2cbce21d4c1fec55137f5
|
||||||
init: true
|
init: true
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
stop_grace_period: 1m
|
stop_grace_period: 1m
|
||||||
|
|||||||
@@ -533,6 +533,67 @@ export default function Webmail() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hydrateFolderFully = async (folderName, authOverride = authPayload, credOverride = credentials) => {
|
||||||
|
const folder = folderName || activeFolder || 'INBOX';
|
||||||
|
const accountId = makeAccountId(authOverride);
|
||||||
|
const direction = String(sortOrder || 'newest');
|
||||||
|
const seen = new Set();
|
||||||
|
const merged = [];
|
||||||
|
let offset = 0;
|
||||||
|
let total = 0;
|
||||||
|
let hasMore = true;
|
||||||
|
let guard = 0;
|
||||||
|
|
||||||
|
while (hasMore && guard < 200) {
|
||||||
|
const data = await postJson('/api/webmail/inbox', {
|
||||||
|
...authOverride,
|
||||||
|
folder,
|
||||||
|
offset,
|
||||||
|
limit: INBOX_PAGE_SIZE,
|
||||||
|
fetchAll: false,
|
||||||
|
direction
|
||||||
|
});
|
||||||
|
|
||||||
|
const batch = Array.isArray(data?.emails) ? data.emails : [];
|
||||||
|
for (const msg of batch) {
|
||||||
|
const uid = Number(msg?.id || 0);
|
||||||
|
if (!seen.has(uid)) {
|
||||||
|
seen.add(uid);
|
||||||
|
merged.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total = Number(data?.total || Math.max(total, merged.length));
|
||||||
|
const nextOffset = Number(data?.nextOffset || (offset + batch.length));
|
||||||
|
const canAdvance = nextOffset > offset;
|
||||||
|
hasMore = Boolean(data?.hasMore) && canAdvance && batch.length > 0;
|
||||||
|
offset = nextOffset;
|
||||||
|
guard += 1;
|
||||||
|
|
||||||
|
if (activeFolderRef.current !== folder || activeAccountIdRef.current !== accountId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeTotal = Math.max(total, merged.length);
|
||||||
|
const paging = {
|
||||||
|
total: safeTotal,
|
||||||
|
nextOffset: merged.length,
|
||||||
|
hasMore: merged.length < safeTotal,
|
||||||
|
loadedAll: merged.length >= safeTotal,
|
||||||
|
direction,
|
||||||
|
updatedAt: Date.now()
|
||||||
|
};
|
||||||
|
setEmails([...merged]);
|
||||||
|
setFolderPaging(accountId, folder, paging);
|
||||||
|
setViewCache(accountId, folder, direction, merged, paging);
|
||||||
|
upsertAccountCache({
|
||||||
|
accountId,
|
||||||
|
folderName: folder,
|
||||||
|
folderEmails: merged
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const connectWithCredentials = async (auth, lastFolder, credOverride = credentials, options = {}) => {
|
const connectWithCredentials = async (auth, lastFolder, credOverride = credentials, options = {}) => {
|
||||||
const { skipInitialFetchIfCached = false } = options;
|
const { skipInitialFetchIfCached = false } = options;
|
||||||
try {
|
try {
|
||||||
@@ -573,9 +634,9 @@ export default function Webmail() {
|
|||||||
await fetchInbox(defaultFolder, auth, true, credOverride, {
|
await fetchInbox(defaultFolder, auth, true, credOverride, {
|
||||||
keepSelection: true,
|
keepSelection: true,
|
||||||
silent: Boolean(cachedDefaultEmails) || skipInitialFetchIfCached,
|
silent: Boolean(cachedDefaultEmails) || skipInitialFetchIfCached,
|
||||||
fetchAll: true,
|
resetToFirstPage: true
|
||||||
resetToFirstPage: false
|
|
||||||
});
|
});
|
||||||
|
await hydrateFolderFully(defaultFolder, auth, credOverride);
|
||||||
saveSession(credOverride, defaultFolder);
|
saveSession(credOverride, defaultFolder);
|
||||||
upsertAccount(credOverride, defaultFolder);
|
upsertAccount(credOverride, defaultFolder);
|
||||||
return true;
|
return true;
|
||||||
@@ -1312,12 +1373,14 @@ export default function Webmail() {
|
|||||||
setIsRefreshingInbox(true);
|
setIsRefreshingInbox(true);
|
||||||
try {
|
try {
|
||||||
const shouldFetchAll = !Boolean(activeFolderPaging?.loadedAll);
|
const shouldFetchAll = !Boolean(activeFolderPaging?.loadedAll);
|
||||||
|
if (shouldFetchAll) {
|
||||||
|
await hydrateFolderFully(activeFolder, authPayload, credentials);
|
||||||
|
} else {
|
||||||
await fetchInbox(activeFolder, authPayload, true, credentials, {
|
await fetchInbox(activeFolder, authPayload, true, credentials, {
|
||||||
keepSelection: true,
|
keepSelection: true,
|
||||||
syncOnly: !shouldFetchAll,
|
syncOnly: true
|
||||||
fetchAll: shouldFetchAll,
|
|
||||||
forceRefresh: shouldFetchAll
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
await fetchFolderSizes(authPayload);
|
await fetchFolderSizes(authPayload);
|
||||||
await fetchQuota(authPayload);
|
await fetchQuota(authPayload);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ name: Webmail
|
|||||||
tagline: Self-hosted IMAP/SMTP webmail client
|
tagline: Self-hosted IMAP/SMTP webmail client
|
||||||
icon: https://git.weektab.org/nexus/umbrel-apps/raw/branch/main/gallery/webmail/icon.png
|
icon: https://git.weektab.org/nexus/umbrel-apps/raw/branch/main/gallery/webmail/icon.png
|
||||||
category: utilities
|
category: utilities
|
||||||
version: "1.0.7"
|
version: "1.0.8"
|
||||||
port: 3001
|
port: 3001
|
||||||
description: >-
|
description: >-
|
||||||
Webmail is a lightweight, self-hosted webmail app for connecting to external
|
Webmail is a lightweight, self-hosted webmail app for connecting to external
|
||||||
@@ -17,7 +17,7 @@ repo: https://git.weektab.org/nexus/webmail
|
|||||||
support: https://git.weektab.org/nexus/webmail/issues
|
support: https://git.weektab.org/nexus/webmail/issues
|
||||||
gallery: []
|
gallery: []
|
||||||
releaseNotes: >-
|
releaseNotes: >-
|
||||||
Improved IMAP full-sync reliability for large inboxes in container deployments.
|
Added robust paged auto-hydration after login to improve sync consistency in Docker.
|
||||||
dependencies: []
|
dependencies: []
|
||||||
path: ""
|
path: ""
|
||||||
defaultUsername: ""
|
defaultUsername: ""
|
||||||
|
|||||||
Reference in New Issue
Block a user