@glance-apps/sync 1.1.1 → 1.1.3
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/CHANGELOG.md +14 -1
- package/package.json +1 -1
- package/src/engine.js +18 -16
- package/src/providers.js +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.1.2] - 2026-06-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **412 retry — local DB mutated before upload**: `applyPayload` was called before
|
|
15
|
+
`upload()` in `doCycle()`. If the upload returned a 412 Precondition Failed (concurrent
|
|
16
|
+
write from another device), the retry cycle re-merged already-dirtied local state with
|
|
17
|
+
the freshly downloaded remote, producing inconsistent data (e.g. completion events
|
|
18
|
+
referencing chore sync IDs that no longer exist after the second merge). `applyPayload`
|
|
19
|
+
is now deferred until after a successful upload (204), leaving the local DB untouched
|
|
20
|
+
if the upload fails so the retry starts from a clean state.
|
|
21
|
+
|
|
10
22
|
## [1.1.1] - 2026-06-03
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -107,7 +119,8 @@ entire sync infrastructure can be shared across the GLANCE app family.
|
|
|
107
119
|
- TypeScript declarations in `types/index.d.ts`.
|
|
108
120
|
- WebDAV CORS proxy handler in `api/webdav-proxy.js`.
|
|
109
121
|
|
|
110
|
-
[Unreleased]: https://github.com/glance-apps/glance-sync/compare/v1.1.
|
|
122
|
+
[Unreleased]: https://github.com/glance-apps/glance-sync/compare/v1.1.2...HEAD
|
|
123
|
+
[1.1.2]: https://github.com/glance-apps/glance-sync/compare/v1.1.1...v1.1.2
|
|
111
124
|
[1.1.1]: https://github.com/glance-apps/glance-sync/compare/v1.1.0...v1.1.1
|
|
112
125
|
[1.1.0]: https://github.com/glance-apps/glance-sync/compare/v1.0.3...v1.1.0
|
|
113
126
|
[1.0.3]: https://github.com/glance-apps/glance-sync/compare/v1.0.2...v1.0.3
|
package/package.json
CHANGED
package/src/engine.js
CHANGED
|
@@ -346,34 +346,36 @@ export const createSyncEngine = (config) => {
|
|
|
346
346
|
const mergeResult = mergePayloads(localData, remote.data);
|
|
347
347
|
const { data: mergedData, localChanged, remoteChanged } = mergeResult;
|
|
348
348
|
|
|
349
|
-
if (localChanged) {
|
|
350
|
-
let mayApply = true;
|
|
351
|
-
if (validateApplyPayload) {
|
|
352
|
-
const check = await validateApplyPayload({ ...remote, data: mergedData });
|
|
353
|
-
if (!check?.valid) {
|
|
354
|
-
// eslint-disable-next-line no-console
|
|
355
|
-
console.error(`[${appId}] applyRemoteData aborted:`, check?.reason || 'validation failed');
|
|
356
|
-
mayApply = false;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (mayApply) {
|
|
360
|
-
await applyPayload(mergedData, { allowEmpty: !!remote.lastModified });
|
|
361
|
-
localStorage.setItem(KEY_LOCAL_MOD, new Date().toISOString());
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
349
|
if (remoteChanged || localChanged) {
|
|
366
350
|
const mergedPayload = {
|
|
367
351
|
version: ENVELOPE_DATA_VERSION,
|
|
368
352
|
lastModified: new Date().toISOString(),
|
|
369
353
|
data: mergedData,
|
|
370
354
|
};
|
|
355
|
+
// Upload first — if this throws (e.g. 412 PRECONDITION_FAILED), the
|
|
356
|
+
// local DB must remain untouched so the retry starts from clean state.
|
|
371
357
|
await upload({
|
|
372
358
|
prebuiltPayload: mergedPayload,
|
|
373
359
|
skipLockCheck: true,
|
|
374
360
|
etag: etag || overrideEtag || null,
|
|
375
361
|
});
|
|
376
362
|
|
|
363
|
+
if (localChanged) {
|
|
364
|
+
let mayApply = true;
|
|
365
|
+
if (validateApplyPayload) {
|
|
366
|
+
const check = await validateApplyPayload({ ...remote, data: mergedData });
|
|
367
|
+
if (!check?.valid) {
|
|
368
|
+
// eslint-disable-next-line no-console
|
|
369
|
+
console.error(`[${appId}] applyRemoteData aborted:`, check?.reason || 'validation failed');
|
|
370
|
+
mayApply = false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (mayApply) {
|
|
374
|
+
await applyPayload(mergedData, { allowEmpty: !!remote.lastModified });
|
|
375
|
+
localStorage.setItem(KEY_LOCAL_MOD, new Date().toISOString());
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
377
379
|
if (hasNeverSynced && localChanged) {
|
|
378
380
|
onFirstSyncReload?.();
|
|
379
381
|
return true;
|
package/src/providers.js
CHANGED
|
@@ -246,7 +246,12 @@ export function createProviders(engineConfig) {
|
|
|
246
246
|
// nativeHttpRequest != null means the Android HTTP bridge is available,
|
|
247
247
|
// which bypasses Koofr's server-side block on Vercel proxy IPs.
|
|
248
248
|
const onAndroid = engineConfig.nativeHttpRequest != null;
|
|
249
|
-
if (!onAndroid)
|
|
249
|
+
if (!onAndroid) {
|
|
250
|
+
if (engineConfig.isHostedApp) {
|
|
251
|
+
return { success: false, error: "Koofr blocks requests from dayglance.app's servers. Use a self-hosted instance or the Android app to connect directly." };
|
|
252
|
+
}
|
|
253
|
+
return { success: false, error: "Access forbidden. If requests are going through a proxy, Koofr may be blocking that server's IP." };
|
|
254
|
+
}
|
|
250
255
|
return { success: false, error: 'Access forbidden. Check that your app password is correct and has not been revoked.' };
|
|
251
256
|
}
|
|
252
257
|
return { success: false, error: `Unexpected response: ${res.status}${res.statusText ? ' ' + res.statusText : ''}` };
|