diff --git a/packages/lib/Synchronizer.ts b/packages/lib/Synchronizer.ts index de67a613a8..d5f27f5f90 100644 --- a/packages/lib/Synchronizer.ts +++ b/packages/lib/Synchronizer.ts @@ -20,7 +20,7 @@ import JoplinError from './JoplinError'; import ShareService from './services/share/ShareService'; import TaskQueue from './TaskQueue'; import ItemUploader from './services/synchronizer/ItemUploader'; -import { FileApi, getSupportsDeltaWithItems, PaginatedList, RemoteItem } from './file-api'; +import { FileApi, getSupportsDeltaWithItems, isLocalServer, PaginatedList, RemoteItem } from './file-api'; import JoplinDatabase from './JoplinDatabase'; import { checkIfCanSync, fetchSyncInfo, checkSyncTargetIsValid, getActiveMasterKey, localSyncInfo, mergeSyncInfos, saveLocalSyncInfo, setMasterKeyHasBeenUsed, SyncInfo, syncInfoEquals, uploadSyncInfo } from './services/synchronizer/syncInfoUtils'; import { getMasterPassword, setupAndDisableEncryption, setupAndEnableEncryption } from './services/e2ee/utils'; @@ -32,6 +32,7 @@ import syncDeleteStep from './services/synchronizer/utils/syncDeleteStep'; import { ErrorCode } from './errors'; import { SyncAction } from './services/synchronizer/utils/types'; import checkDisabledSyncItemsNotification from './services/synchronizer/utils/checkDisabledSyncItemsNotification'; +import SyncTargetRegistry from './SyncTargetRegistry'; const { sprintf } = require('sprintf-js'); const { Dirnames } = require('./services/synchronizer/utils/types'); @@ -1164,8 +1165,13 @@ export default class Synchronizer { logger.error(error); if (error.details) logger.error('Details:', error.details); - // Don't save to the report errors that are due to things like temporary network errors or timeout. - if (!shim.fetchRequestCanBeRetried(error)) { + const isLocalWebDavServer = Setting.value('sync.target') === SyncTargetRegistry.nameToId('webdav') && isLocalServer(Setting.value('sync.6.path')); + + // Don't save to the report errors that are due to things like temporary network errors or timeout, except if using a local WebDAV server, in which + // case timeout errors can occur when the server is actually down. Those type of errors happen consistently when a local server is down when using + // the Android app in particular, but they can also happen on the desktop app in some circumstances. The usage of a local WebDAV server is most useful + // on Android, as it can be used as an alternative to file system sync, in order to work around performance issues related to SAF + if (!shim.fetchRequestCanBeRetried(error) || isLocalWebDavServer) { this.progressReport_.errors.push(error); this.logLastRequests(); } diff --git a/packages/lib/file-api.test.ts b/packages/lib/file-api.test.ts index 7717163df8..c7714e7226 100644 --- a/packages/lib/file-api.test.ts +++ b/packages/lib/file-api.test.ts @@ -1,4 +1,4 @@ -import { PaginatedList, RemoteItem, getSupportsDeltaWithItems } from './file-api'; +import { PaginatedList, RemoteItem, getSupportsDeltaWithItems, isLocalServer } from './file-api'; const defaultPaginatedList = (): PaginatedList => { return { @@ -70,4 +70,28 @@ describe('file-api', () => { expect(actual).toBe(expected); }); + it.each([ + 'http://localhost', + 'http://localhost/', + 'https://localhost:8080', + 'http://127.0.0.1', + 'https://127.100.50.25:3000/test', + 'http://[::1]', + 'http://localhost/api/v1', + ])('should detect a local server url', (url: string) => { + const result = isLocalServer(url); + expect(result).toBe(true); + }); + + it.each([ + 'http://localhostXYZ', + 'http://127.0.0.1foobar', + 'http://192.168.1.1', + 'http://example.com', + 'https://my-localhost.com', + ])('should detect a non local server url', (url: string) => { + const result = isLocalServer(url); + expect(result).toBe(false); + }); + }); diff --git a/packages/lib/file-api.ts b/packages/lib/file-api.ts index b35ffe5aa5..47f12e5f81 100644 --- a/packages/lib/file-api.ts +++ b/packages/lib/file-api.ts @@ -53,6 +53,11 @@ export const getSupportsDeltaWithItems = (deltaResponse: PaginatedList) => { return 'jopItem' in deltaResponse.items[0]; }; +export const isLocalServer = (url: string) => { + const regex = /^(https?:\/\/)?(localhost|127(?:\.\d{1,3}){3}|\[::1\])(?::\d{1,5})?(\/.*)?$/i; + return regex.test(url); +}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied function requestCanBeRepeated(error: any) { const errorCode = typeof error === 'object' && error.code ? error.code : null;