@telecrypt-io/storage 0.7.2 → 0.9.0

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/README.md CHANGED
@@ -46,6 +46,13 @@ by default. Browser persistence uses IndexedDB and the Matrix Rust crypto WASM r
46
46
  example assumes a browser environment. OIDC discovery, login, refresh, and the corresponding
47
47
  `createFromOidc` inputs are exported from `@telecrypt-io/storage/core`.
48
48
 
49
+ Applications that require the Decryption Key Safe before downloading room history can create the
50
+ client with `startClient: false`. Check its status and restore room keys first; `restore()` starts
51
+ sync after importing them so Matrix can publish this login's device keys and complete account
52
+ signing. For an already-ready safe, call `await storage.startSync()` after the status check. Keep
53
+ Storage operations locked until the Safe reports ready. The default is to start sync during
54
+ creation.
55
+
49
56
  The example creates retained Matrix rooms and encrypted media. Delete its file and then its empty
50
57
  vault when finished; cleanup is part of the operation, not an automatic consequence of stopping
51
58
  the client.
@@ -62,14 +69,41 @@ The SDK maps the file tree to Matrix primitives defined by MSC3089:
62
69
  Vault, folder, and file IDs are opaque Matrix room and event IDs. Names are display labels: two
63
70
  objects with the same name are still distinct, and a name is not an idempotency key.
64
71
 
72
+ Vault and folder names, and file names, are stored in encrypted room messages. Matrix room names
73
+ remain the generic `Encrypted storage`; file-listing state stores only an event pointer, and media
74
+ uploads omit the original filename. Use the asynchronous operations in `@telecrypt-io/storage/core`
75
+ to read names. The synchronous Matrix SDK tree helper cannot decrypt on demand and exposes a generic
76
+ file label until an async operation resolves the name. This is a TeleCrypt metadata format: standard
77
+ MSC3089 clients will not display the private names or understand these file indexes. SDK 0.8 starts
78
+ this format without a reader or migration for trees written by older SDK versions; those old trees
79
+ may be ignored or removed.
80
+
65
81
  File bytes are encrypted in the client before upload with Matrix attachment encryption (AES-CTR
66
82
  and a per-file key distributed through the room's Megolm session). The server stores encrypted
67
83
  media and Matrix events, but encryption does not hide all room, event, or other metadata from the
68
84
  server or users who can read the room.
69
85
 
70
- Recovery is explicit. Use `core.setupRecovery(storage)` to create a recovery key and keep that key
71
- securely; use `core.restoreRecovery(storage, recoveryKey)` on a new device. Losing the recovery key
72
- can prevent encrypted keys from being restored.
86
+ The Decryption Key Safe is mandatory before the public core storage operations. Inspect
87
+ `storage.keySafe.getStatus(signal?)`. For `setup-required`, call `setup(signal?)`, offer the returned
88
+ `recoveryKey` for copying/saving, explain that losing both this key and local decryption keys can
89
+ make files permanently unreadable, and require an explicit saved-key acknowledgement before
90
+ `confirmSaved(signal?)`. `confirmation-required` returns the same pending key after interruption.
91
+ For `restore-required`, call `restore(recoveryKey, signal?)` with the existing key; this restores
92
+ room keys and signs the current login using the existing account keys. Normal use requires `ready`.
93
+ Setup and restore never replace an existing safe or account signing identity. Resetting the account
94
+ password cannot recover encrypted files.
95
+
96
+ Pending setup state is kept beside the login's IndexedDB crypto data. A platform that snapshots
97
+ IndexedDB to disk, such as the CLI, must supply `onKeySafeStateChanged` at creation and durably flush
98
+ its snapshot before resolving that callback. Key Safe methods accept an optional `AbortSignal`.
99
+ The safe, signing keys, and saved-key acknowledgement are shared SDK behavior rather than separate
100
+ Web/CLI implementations.
101
+
102
+ Sharing uses Matrix's native encrypted-history bundle exchange. Both users must have completed
103
+ their account signing setup. An invitation alone does not establish successful history sharing:
104
+ the SDK checks native recipient-device eligibility afterward and reports partial completion if
105
+ keys could not be shared. An upload restriction can reject the encrypted history bundle upload;
106
+ the error is preserved, including when retrying an existing invitation.
73
107
 
74
108
  Deletion has strict ordering. Delete files before their folders or vaults because folders and
75
109
  vaults must be empty. File deletion removes the encrypted media object before redacting its Matrix
@@ -2,6 +2,7 @@ import { MatrixClient, MatrixEvent } from "matrix-js-sdk";
2
2
  import type { CryptoCallbacks } from "matrix-js-sdk/lib/crypto-api/index.js";
3
3
  import type { TokenRefreshFunction } from "matrix-js-sdk/lib/http-api/index.js";
4
4
  import type { RecoveryStatus } from "./core/types.js";
5
+ import { DecryptionKeySafe, type KeySafePersistenceOptions } from "./key-safe.js";
5
6
  export interface TreeSpace {
6
7
  readonly id: string;
7
8
  readonly room: {
@@ -26,8 +27,9 @@ export interface TreeSpace {
26
27
  }
27
28
  export interface FileBranch {
28
29
  readonly id: string;
30
+ readonly roomId: string;
29
31
  /** Current MSC3089 branch/listing event. Its event ID is distinct from `id` (the state key). */
30
- readonly indexEvent?: Pick<MatrixEvent, "getId">;
32
+ readonly indexEvent?: Pick<MatrixEvent, "getId" | "getContent" | "getSender" | "isRedacted">;
31
33
  /** Matrix SDK keeps a redacted branch in state as an inactive object. */
32
34
  readonly isActive?: boolean;
33
35
  getName(): string;
@@ -63,8 +65,12 @@ export interface CreateTeleCryptIOStorageOptions {
63
65
  * Node/tests, where `fake-indexeddb` is process-global).
64
66
  */
65
67
  cryptoDatabasePrefix?: string;
68
+ /** Persist local Key Safe state before server-side setup continues (CLI snapshots). */
69
+ onKeySafeStateChanged?: () => Promise<void>;
66
70
  /** initialSyncLimit passed to startClient(); default 10. */
67
71
  initialSyncLimit?: number;
72
+ /** Start Matrix sync after crypto initializes; default true. */
73
+ startClient?: boolean;
68
74
  /** How long to wait for the first sync before giving up; default 15000ms. */
69
75
  syncTimeoutMs?: number;
70
76
  /** How long to wait for rust-crypto WASM + IndexedDB init; default 60000ms. */
@@ -106,7 +112,10 @@ export interface CreateFromOidcOptions {
106
112
  tokenRefreshFunction?: TokenRefreshFunction;
107
113
  persistentCryptoStore?: boolean;
108
114
  cryptoDatabasePrefix?: string;
115
+ /** Persist local Key Safe state before server-side setup continues (CLI snapshots). */
116
+ onKeySafeStateChanged?: () => Promise<void>;
109
117
  initialSyncLimit?: number;
118
+ startClient?: boolean;
110
119
  syncTimeoutMs?: number;
111
120
  initTimeoutMs?: number;
112
121
  onProgress?: (message: string) => void;
@@ -143,10 +152,40 @@ export declare function readFileEventMetadata(content: unknown): FileEventMetada
143
152
  export declare function withTreeMutation<T>(client: MatrixClient, operation: (signal?: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T>;
144
153
  export declare class TeleCryptIOStorage {
145
154
  private client;
155
+ private readonly syncOptions;
146
156
  private readonly decoratedTreeSpaces;
147
- constructor(client: MatrixClient);
157
+ private readonly decoratedFileBranches;
158
+ private readonly treeNames;
159
+ private readonly fileNames;
160
+ readonly keySafe: DecryptionKeySafe;
161
+ private syncStarted;
162
+ private syncInFlight;
163
+ constructor(client: MatrixClient, options?: KeySafePersistenceOptions, syncOptions?: Pick<CreateTeleCryptIOStorageOptions, "initialSyncLimit" | "syncTimeoutMs" | "onProgress">);
148
164
  /** The underlying matrix-js-sdk client (e.g. to stop it, or for advanced/interop use). */
149
165
  getClient(): MatrixClient;
166
+ /** Start Matrix room sync after crypto setup, for example after the current
167
+ * login has checked or restored its Decryption Key Safe. */
168
+ startSync(signal?: AbortSignal): Promise<void>;
169
+ private fileNameKey;
170
+ private getStateEvent;
171
+ private getTreeOwnerId;
172
+ private sendEncryptedMetadataMessage;
173
+ private readEncryptedMetadataMessage;
174
+ private writeTreeName;
175
+ /** Resolves an encrypted vault/folder name through its pointer state event. */
176
+ getTreeName(roomId: string, options?: MatrixRequestOptions): Promise<string>;
177
+ private writeFileName;
178
+ /** Resolves an encrypted file name from the MSC3089 index pointer. */
179
+ getFileName(treeId: string, fileId: string, options?: MatrixRequestOptions & {
180
+ refreshState?: boolean;
181
+ }): Promise<string>;
182
+ /** Returns the current file metadata pointer, including on inactive branches. */
183
+ getFileMetadataEventId(treeId: string, fileId: string, options?: MatrixRequestOptions): Promise<string | null>;
184
+ /** Fetches and validates a current encrypted rename event, allowing retries after redaction. */
185
+ getFileRenameMetadataEvent(treeId: string, fileId: string, metadataEventId: string): Promise<MatrixEvent>;
186
+ /** Fetches the original encrypted file message directly by event ID. */
187
+ getOriginalFileEvent(treeId: string, fileId: string, allowRedacted?: boolean): Promise<MatrixEvent>;
188
+ private createEncryptedFile;
150
189
  /**
151
190
  * Refreshes a room's current state from the homeserver. The background sync
152
191
  * loop is intentionally asynchronous, so a short-lived CLI process can
@@ -177,7 +216,7 @@ export declare class TeleCryptIOStorage {
177
216
  */
178
217
  static createFromOidc(opts: CreateFromOidcOptions): Promise<TeleCryptIOStorage>;
179
218
  /** Shared post-construction bootstrap for `create()`/`createFromOidc()`:
180
- * persistent crypto store, first sync, wrap in a `TeleCryptIOStorage`. */
219
+ * persistent crypto store, optional first sync, wrap in a `TeleCryptIOStorage`. */
181
220
  private static bootstrap;
182
221
  private static throwIfAborted;
183
222
  private static withTimeout;
@@ -249,6 +288,7 @@ export declare class TeleCryptIOStorage {
249
288
  * which refreshes the exact parent/child state before reporting success.
250
289
  */
251
290
  private decorateTreeSpace;
291
+ private decorateFileBranch;
252
292
  /** Cleans up only a room created by this operation and reports incomplete cleanup. */
253
293
  private cleanupCreatedRoom;
254
294
  private removeRelationLink;