@telecrypt-io/storage 0.8.0 → 0.9.2

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/LICENSE CHANGED
@@ -2,7 +2,7 @@ Business Source License 1.1
2
2
 
3
3
  Parameters
4
4
 
5
- Licensor: TeleCrypt.io
5
+ Licensor: Alexander Potemkin
6
6
  Licensed Work: TeleCrypt.io Storage
7
7
  The Licensed Work is (c) 2026 TeleCrypt.io
8
8
  Additional Use Grant: You may use, copy, modify, and create derivative
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.
@@ -76,9 +83,56 @@ and a per-file key distributed through the room's Megolm session). The server st
76
83
  media and Matrix events, but encryption does not hide all room, event, or other metadata from the
77
84
  server or users who can read the room.
78
85
 
79
- Recovery is explicit. Use `core.setupRecovery(storage)` to create a recovery key and keep that key
80
- securely; use `core.restoreRecovery(storage, recoveryKey)` on a new device. Losing the recovery key
81
- can prevent encrypted keys from being restored.
86
+ If an upload is rejected after media was accepted, the SDK removes that media and any acknowledged
87
+ attachment. A transport failure leaves accepted work intact. An upload `MutationPartialError`
88
+ retains `treeId` and `completedIds` in the order `[mediaId, attachmentEventId?]`. Keep those IDs;
89
+ do not repeat the upload blindly. To discard the partial upload, call
90
+ `core.cleanupFileUpload(storage, error.treeId, error.completedIds[0], error.completedIds[1])`.
91
+ Retry cleanup with the same IDs if it fails. A known attachment ID can also be passed to ordinary
92
+ `core.deleteFile` even when its listing was never committed. An initial media response lost before
93
+ the server returns any identifier still has an unknown outcome and cannot provide cleanup IDs.
94
+ Upload cancellation stops before the next write and waits for the current request's bounded
95
+ settlement so returned media/event IDs are retained. Keep observing the upload promise after
96
+ cancelling a UI wait or closing a view; persist any partial receipt for the originating account.
97
+
98
+ The Decryption Key Safe is mandatory before the public core storage operations. Inspect
99
+ `storage.keySafe.getStatus(signal?)`. For `setup-required`, call `setup(signal?)`, offer the returned
100
+ `recoveryKey` for copying/saving, explain that losing both this key and local decryption keys can
101
+ make files permanently unreadable, and require an explicit saved-key acknowledgement before
102
+ `confirmSaved(signal?)`. `confirmation-required` returns the same pending key after interruption.
103
+ For `restore-required`, call `restore(recoveryKey, signal?)` with the existing key; this restores
104
+ room keys and signs the current login using the existing account keys. Normal use requires `ready`.
105
+ Setup and restore never replace an existing safe or account signing identity. Resetting the account
106
+ password cannot recover encrypted files.
107
+
108
+ Short-lived clients should call `storage.waitForKeyBackup(treeId, fileId?, options?)` after creating
109
+ or renaming encrypted metadata and before closing. Omit `fileId` for a vault or folder. This checks
110
+ the exact encryption session in the active server backup, including reused keys that produce no
111
+ new backup-progress event. If verification fails, retain the local profile and retry backup;
112
+ the metadata mutation has already committed.
113
+
114
+ Pending setup state is kept beside the login's IndexedDB crypto data. A platform that snapshots
115
+ IndexedDB to disk, such as the CLI, must supply `onKeySafeStateChanged` at creation and durably flush
116
+ its snapshot before resolving that callback. Key Safe methods accept an optional `AbortSignal`.
117
+ The safe, signing keys, and saved-key acknowledgement are shared SDK behavior rather than separate
118
+ Web/CLI implementations.
119
+
120
+ Sharing uses Matrix's native encrypted-history bundle exchange. Both users must have completed
121
+ their account signing setup. An invitation alone does not establish successful history sharing:
122
+ the SDK checks native recipient-device eligibility afterward and reports partial completion if
123
+ keys could not be shared. An upload restriction can reject the encrypted history bundle upload;
124
+ the error is preserved, including when retrying an existing invitation.
125
+
126
+ `joinVault` accepts the vault and its linked folder invitations, including their history keys.
127
+ New folders inherit the parent's joined and invited readers; browsing accepts their folder
128
+ invitations. If reader access fails after folder creation, the partial-completion error retains
129
+ the created folder ID. Retry sharing the parent vault to complete access to that folder.
130
+
131
+ Storage spaces are private and only their creator changes room state. Loading owned trees or
132
+ refreshing their state repairs the permissive defaults emitted by older SDKs and restores
133
+ invite-only joining. The repair preserves membership, unrelated power settings and encryption
134
+ keys; readers do not write it. Owners must use an updated client for their existing trees to be
135
+ repaired. Disabled Storage markers and deleted trees are not restored automatically.
82
136
 
83
137
  Deletion has strict ordering. Delete files before their folders or vaults because folders and
84
138
  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: {
@@ -20,7 +21,7 @@ export interface TreeSpace {
20
21
  setPermissions(userId: string, role: string): Promise<void>;
21
22
  getFile(fileEventId: string): FileBranch | null;
22
23
  listFiles(): FileBranch[];
23
- createFile(name: string, encryptedContents: ArrayBuffer | Uint8Array, info: Record<string, unknown>, additionalContent?: Record<string, unknown>): Promise<{
24
+ createFile(name: string, encryptedContents: ArrayBuffer | Uint8Array, info: Record<string, unknown>, additionalContent?: Record<string, unknown>, signal?: AbortSignal): Promise<{
24
25
  event_id: string;
25
26
  }>;
26
27
  }
@@ -64,8 +65,12 @@ export interface CreateTeleCryptIOStorageOptions {
64
65
  * Node/tests, where `fake-indexeddb` is process-global).
65
66
  */
66
67
  cryptoDatabasePrefix?: string;
68
+ /** Persist local Key Safe state before server-side setup continues (CLI snapshots). */
69
+ onKeySafeStateChanged?: () => Promise<void>;
67
70
  /** initialSyncLimit passed to startClient(); default 10. */
68
71
  initialSyncLimit?: number;
72
+ /** Start Matrix sync after crypto initializes; default true. */
73
+ startClient?: boolean;
69
74
  /** How long to wait for the first sync before giving up; default 15000ms. */
70
75
  syncTimeoutMs?: number;
71
76
  /** How long to wait for rust-crypto WASM + IndexedDB init; default 60000ms. */
@@ -107,7 +112,10 @@ export interface CreateFromOidcOptions {
107
112
  tokenRefreshFunction?: TokenRefreshFunction;
108
113
  persistentCryptoStore?: boolean;
109
114
  cryptoDatabasePrefix?: string;
115
+ /** Persist local Key Safe state before server-side setup continues (CLI snapshots). */
116
+ onKeySafeStateChanged?: () => Promise<void>;
110
117
  initialSyncLimit?: number;
118
+ startClient?: boolean;
111
119
  syncTimeoutMs?: number;
112
120
  initTimeoutMs?: number;
113
121
  onProgress?: (message: string) => void;
@@ -144,21 +152,35 @@ export declare function readFileEventMetadata(content: unknown): FileEventMetada
144
152
  export declare function withTreeMutation<T>(client: MatrixClient, operation: (signal?: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T>;
145
153
  export declare class TeleCryptIOStorage {
146
154
  private client;
155
+ private readonly syncOptions;
147
156
  private readonly decoratedTreeSpaces;
148
157
  private readonly decoratedFileBranches;
149
158
  private readonly treeNames;
150
159
  private readonly fileNames;
151
- constructor(client: MatrixClient);
160
+ readonly keySafe: DecryptionKeySafe;
161
+ private syncStarted;
162
+ private syncInFlight;
163
+ constructor(client: MatrixClient, options?: KeySafePersistenceOptions, syncOptions?: Pick<CreateTeleCryptIOStorageOptions, "initialSyncLimit" | "syncTimeoutMs" | "onProgress">);
152
164
  /** The underlying matrix-js-sdk client (e.g. to stop it, or for advanced/interop use). */
153
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>;
154
169
  private fileNameKey;
155
170
  private getStateEvent;
156
171
  private getTreeOwnerId;
157
172
  private sendEncryptedMetadataMessage;
158
173
  private readEncryptedMetadataMessage;
159
174
  private writeTreeName;
175
+ private getTreeMetadataEvent;
160
176
  /** Resolves an encrypted vault/folder name through its pointer state event. */
161
177
  getTreeName(roomId: string, options?: MatrixRequestOptions): Promise<string>;
178
+ /**
179
+ * Waits for the current encrypted vault/folder name, or file metadata, to
180
+ * have its session in the active native key backup. Checks the exact session
181
+ * even when encryption reuses a key and the backup engine emits no new event.
182
+ */
183
+ waitForKeyBackup(treeId: string, fileId?: string, options?: MatrixRequestOptions): Promise<void>;
162
184
  private writeFileName;
163
185
  /** Resolves an encrypted file name from the MSC3089 index pointer. */
164
186
  getFileName(treeId: string, fileId: string, options?: MatrixRequestOptions & {
@@ -171,6 +193,12 @@ export declare class TeleCryptIOStorage {
171
193
  /** Fetches the original encrypted file message directly by event ID. */
172
194
  getOriginalFileEvent(treeId: string, fileId: string, allowRedacted?: boolean): Promise<MatrixEvent>;
173
195
  private createEncryptedFile;
196
+ /**
197
+ * Discards a partial upload using its MutationPartialError.completedIds:
198
+ * [mediaId, fileId?]. Explicit cleanup also covers an unacknowledged listing.
199
+ * Keep the same IDs after failure and retry this operation, not the upload.
200
+ */
201
+ cleanupFileUpload(treeId: string, mediaId: string, fileId?: string, options?: MatrixRequestOptions): Promise<void>;
174
202
  /**
175
203
  * Refreshes a room's current state from the homeserver. The background sync
176
204
  * loop is intentionally asynchronous, so a short-lived CLI process can
@@ -178,6 +206,7 @@ export declare class TeleCryptIOStorage {
178
206
  * changed a file name or branch.
179
207
  */
180
208
  refreshRoomState(roomId: string, options?: MatrixRequestOptions): Promise<void>;
209
+ private repairOwnedTreePermissions;
181
210
  /**
182
211
  * Recommended entry point: builds the MatrixClient with a persistent crypto
183
212
  * store and the secret-storage callback wiring `keys.*` needs, starts the
@@ -201,7 +230,7 @@ export declare class TeleCryptIOStorage {
201
230
  */
202
231
  static createFromOidc(opts: CreateFromOidcOptions): Promise<TeleCryptIOStorage>;
203
232
  /** Shared post-construction bootstrap for `create()`/`createFromOidc()`:
204
- * persistent crypto store, first sync, wrap in a `TeleCryptIOStorage`. */
233
+ * persistent crypto store, optional first sync, wrap in a `TeleCryptIOStorage`. */
205
234
  private static bootstrap;
206
235
  private static throwIfAborted;
207
236
  private static withTimeout;