@glance-apps/sync 1.0.3 → 1.1.1
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 +35 -1
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/crypto.js +39 -0
- package/src/index.js +1 -0
- package/src/providers.js +1 -1
- package/types/index.d.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.1.1] - 2026-06-03
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **WebDAV — 405 on MKCOL treated as directory-already-exists**: `mkcolWithParents`
|
|
15
|
+
now handles HTTP 405 (Method Not Allowed) the same as 409/404, triggering the
|
|
16
|
+
parent-directory creation fallback. Some WebDAV servers (e.g. certain nginx
|
|
17
|
+
configurations) return 405 rather than 409 when MKCOL is called on a path that
|
|
18
|
+
already exists as a collection.
|
|
19
|
+
|
|
20
|
+
## [1.1.0] - 2026-05-25
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `deriveKeyForSalt(salt: Uint8Array): Promise<CryptoKey>` — derives a fresh
|
|
25
|
+
non-extractable AES-256-GCM key from the cached passphrase and a caller-supplied
|
|
26
|
+
salt (PBKDF2-SHA-256, 310 000 iterations). Intended as the `deriveKey` callback
|
|
27
|
+
for `@glance-apps/intents` per-envelope key derivation (Phase 2.6): the intents
|
|
28
|
+
emitter generates a random salt per envelope, embeds it in the envelope, and
|
|
29
|
+
calls this to obtain the encryption key; the poller extracts the salt from the
|
|
30
|
+
envelope and calls this again to obtain the decryption key — enabling cross-app
|
|
31
|
+
decryption without sharing a per-app session key.
|
|
32
|
+
|
|
33
|
+
Throws with `err.code === 'PASSPHRASE_REQUIRED'` when no passphrase is held in
|
|
34
|
+
the current session. Note that this guard is on `_sessionPassphrase`, **not** on
|
|
35
|
+
`hasEncryptionReady()`: after `initSessionKey()` restores a key from device
|
|
36
|
+
storage the session key is ready but the passphrase is not. Callers that want to
|
|
37
|
+
gate on passphrase availability should check `getSyncPassphrase() !== null`.
|
|
38
|
+
|
|
39
|
+
`getSessionKey()` (added in 1.0.2) is **not removed**; it remains exported for
|
|
40
|
+
any consumer that wants the cached per-app `CryptoKey` directly.
|
|
41
|
+
|
|
10
42
|
## [1.0.3] - 2026-05-23
|
|
11
43
|
|
|
12
44
|
### Fixed
|
|
@@ -75,7 +107,9 @@ entire sync infrastructure can be shared across the GLANCE app family.
|
|
|
75
107
|
- TypeScript declarations in `types/index.d.ts`.
|
|
76
108
|
- WebDAV CORS proxy handler in `api/webdav-proxy.js`.
|
|
77
109
|
|
|
78
|
-
[Unreleased]: https://github.com/glance-apps/glance-sync/compare/v1.
|
|
110
|
+
[Unreleased]: https://github.com/glance-apps/glance-sync/compare/v1.1.1...HEAD
|
|
111
|
+
[1.1.1]: https://github.com/glance-apps/glance-sync/compare/v1.1.0...v1.1.1
|
|
112
|
+
[1.1.0]: https://github.com/glance-apps/glance-sync/compare/v1.0.3...v1.1.0
|
|
79
113
|
[1.0.3]: https://github.com/glance-apps/glance-sync/compare/v1.0.2...v1.0.3
|
|
80
114
|
[1.0.2]: https://github.com/glance-apps/glance-sync/compare/v1.0.1...v1.0.2
|
|
81
115
|
[1.0.1]: https://github.com/glance-apps/glance-sync/compare/v1.0.0...v1.0.1
|
package/README.md
CHANGED
|
@@ -147,7 +147,8 @@ Pass `encryptionEnabled: true` in the sync config object stored via `engine.setC
|
|
|
147
147
|
| `isEncryptedEnvelope(value)` | Type guard — returns `true` if the value looks like an `EncryptedEnvelope`. |
|
|
148
148
|
| `setSyncPassphrase(p)` / `getSyncPassphrase()` | Store/retrieve the passphrase in session memory (not persisted). |
|
|
149
149
|
| `hasEncryptionReady()` | Returns `true` if the session key is loaded. |
|
|
150
|
-
| `getSessionKey()` | Returns the cached non-extractable `CryptoKey`, or `null` if no key is loaded
|
|
150
|
+
| `getSessionKey()` | Returns the cached non-extractable `CryptoKey`, or `null` if no key is loaded. |
|
|
151
|
+
| `deriveKeyForSalt(salt)` | Derives a fresh non-extractable AES-256-GCM key from the cached passphrase and a caller-supplied 16-byte salt (PBKDF2-SHA-256, 310 000 iterations). Intended as the `deriveKey` callback for `@glance-apps/intents` per-envelope encryption: pass `sync.deriveKeyForSalt` to `buildEncryptedEnvelope` / `parseEncryptedEnvelope` so each envelope is independently rekeyed. Throws with `err.code === 'PASSPHRASE_REQUIRED'` when no passphrase is in session — gate on `getSyncPassphrase() !== null` before using it. |
|
|
151
152
|
|
|
152
153
|
### Transport & providers
|
|
153
154
|
|
package/package.json
CHANGED
package/src/crypto.js
CHANGED
|
@@ -19,6 +19,45 @@ export function getSyncPassphrase() { return _sessionPassphrase; }
|
|
|
19
19
|
export function hasEncryptionReady() { return _sessionKey !== null; }
|
|
20
20
|
export function getSessionKey() { return _sessionKey; }
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Derives a fresh AES-256-GCM key from the cached passphrase and the supplied
|
|
24
|
+
* salt. Intended for per-envelope key derivation in `@glance-apps/intents`:
|
|
25
|
+
* callers generate a new random salt per envelope, embed it in the envelope,
|
|
26
|
+
* and call this to get the encryption key — enabling cross-app decryption with
|
|
27
|
+
* the same passphrase but a fresh salt on every write.
|
|
28
|
+
*
|
|
29
|
+
* Uses identical PBKDF2 parameters as the rest of this module (SHA-256,
|
|
30
|
+
* 310 000 iterations, AES-256-GCM). The returned key is non-extractable because
|
|
31
|
+
* it is ephemeral and never stored.
|
|
32
|
+
*
|
|
33
|
+
* Throws with `err.code === 'PASSPHRASE_REQUIRED'` when no passphrase is
|
|
34
|
+
* held in the current session (e.g. after `initSessionKey()` restores from
|
|
35
|
+
* device storage without the user re-entering their passphrase). Note: this
|
|
36
|
+
* is a different condition from `hasEncryptionReady()` — callers that want to
|
|
37
|
+
* gate on passphrase availability should check `getSyncPassphrase() !== null`.
|
|
38
|
+
*
|
|
39
|
+
* @param {Uint8Array} salt - 16-byte salt to derive the key from
|
|
40
|
+
* @returns {Promise<CryptoKey>}
|
|
41
|
+
*/
|
|
42
|
+
export async function deriveKeyForSalt(salt) {
|
|
43
|
+
if (!_sessionPassphrase) {
|
|
44
|
+
const err = new Error('Encryption passphrase not available. Please enter your sync passphrase.');
|
|
45
|
+
err.code = 'PASSPHRASE_REQUIRED';
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
const enc = new TextEncoder();
|
|
49
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
50
|
+
'raw', enc.encode(_sessionPassphrase), 'PBKDF2', false, ['deriveKey']
|
|
51
|
+
);
|
|
52
|
+
return crypto.subtle.deriveKey(
|
|
53
|
+
{ name: 'PBKDF2', salt, iterations: 310_000, hash: 'SHA-256' },
|
|
54
|
+
keyMaterial,
|
|
55
|
+
{ name: 'AES-GCM', length: 256 },
|
|
56
|
+
false,
|
|
57
|
+
['encrypt', 'decrypt']
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
22
61
|
// ---------------------------------------------------------------------------
|
|
23
62
|
// IndexedDB key store
|
|
24
63
|
// ---------------------------------------------------------------------------
|
package/src/index.js
CHANGED
package/src/providers.js
CHANGED
|
@@ -102,7 +102,7 @@ async function parseDownloadResponse(res, engineConfig) {
|
|
|
102
102
|
|
|
103
103
|
async function mkcolWithParents(dirUrl, authHeaders, wf) {
|
|
104
104
|
const res = await wf('MKCOL', dirUrl, authHeaders);
|
|
105
|
-
if (res.status === 409 || res.status === 404) {
|
|
105
|
+
if (res.status === 409 || res.status === 404 || res.status === 405) {
|
|
106
106
|
// Derive parent by stripping the last path segment via URL() to avoid
|
|
107
107
|
// producing malformed strings on root-level inputs.
|
|
108
108
|
let parent;
|
package/types/index.d.ts
CHANGED
|
@@ -156,6 +156,17 @@ export function setSyncPassphrase(passphrase: string | null): void;
|
|
|
156
156
|
export function getSyncPassphrase(): string | null;
|
|
157
157
|
export function hasEncryptionReady(): boolean;
|
|
158
158
|
export function getSessionKey(): CryptoKey | null;
|
|
159
|
+
/**
|
|
160
|
+
* Derives a fresh non-extractable AES-256-GCM key from the cached passphrase
|
|
161
|
+
* and the supplied salt (PBKDF2-SHA-256, 310 000 iterations). Intended for
|
|
162
|
+
* per-envelope key derivation in `@glance-apps/intents`: pass this function as
|
|
163
|
+
* the `deriveKey` callback to `buildEncryptedEnvelope` / `parseEncryptedEnvelope`.
|
|
164
|
+
*
|
|
165
|
+
* Throws with `err.code === 'PASSPHRASE_REQUIRED'` when no passphrase is held
|
|
166
|
+
* in the current session. Gate on `getSyncPassphrase() !== null` (not
|
|
167
|
+
* `hasEncryptionReady()`) before passing this as a callback.
|
|
168
|
+
*/
|
|
169
|
+
export function deriveKeyForSalt(salt: Uint8Array): Promise<CryptoKey>;
|
|
159
170
|
export function encryptData<T>(data: T, config?: CryptoConfig): Promise<EncryptedEnvelope>;
|
|
160
171
|
export function decryptData<T = unknown>(envelope: EncryptedEnvelope, config?: CryptoConfig): Promise<T>;
|
|
161
172
|
export function isEncryptedEnvelope(value: unknown): value is EncryptedEnvelope;
|