@telecrypt-io/storage 0.1.3 → 0.2.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/dist/cli/poll.js DELETED
@@ -1,3 +0,0 @@
1
- // Re-homed under src/core/poll.ts (platform-agnostic — no Node/CLI
2
- // dependencies), re-exported here so existing CLI imports keep working.
3
- export { waitForCondition } from "../core/poll.js";
@@ -1,33 +0,0 @@
1
- export interface Session {
2
- homeserver: string;
3
- userId: string;
4
- deviceId: string;
5
- accessToken: string;
6
- /**
7
- * Present only when logged in via `storage login --oidc` (device-code
8
- * grant against MAS; see src/cli/oidc.ts).
9
- * Absent for a plain password login. `oidcTokenEndpoint` is persisted
10
- * directly (rather than re-discovered from `oidcIssuer` on every command)
11
- * so reusing/refreshing the session never needs OIDC discovery again —
12
- * discovery is Node-hostile (see src/cli/oidcWindowPolyfill.ts) and this
13
- * avoids needing it more than once, at login time.
14
- */
15
- refreshToken?: string;
16
- oidcIssuer?: string;
17
- oidcClientId?: string;
18
- oidcTokenEndpoint?: string;
19
- }
20
- /**
21
- * Resolves the profile directory: everything this CLI persists (session,
22
- * crypto store) lives here. Overridable via TELECRYPT_IO_STORAGE_HOME so
23
- * tests can give each simulated user/device its own isolated profile.
24
- * Defaults to ~/.telecrypt-io/storage for normal interactive use.
25
- */
26
- export declare function profileDir(): string;
27
- export declare function sessionPath(dir?: string): string;
28
- export declare function cryptoSnapshotPath(dir?: string): string;
29
- export declare function ensureProfileDir(dir?: string): void;
30
- export declare function readSession(dir?: string): Session | null;
31
- export declare function writeSession(session: Session, dir?: string): void;
32
- /** Clears all local state for this profile (session + crypto store). */
33
- export declare function clearProfile(dir?: string): void;
@@ -1,46 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import * as os from "node:os";
4
- /**
5
- * Resolves the profile directory: everything this CLI persists (session,
6
- * crypto store) lives here. Overridable via TELECRYPT_IO_STORAGE_HOME so
7
- * tests can give each simulated user/device its own isolated profile.
8
- * Defaults to ~/.telecrypt-io/storage for normal interactive use.
9
- */
10
- export function profileDir() {
11
- const home = process.env.TELECRYPT_IO_STORAGE_HOME;
12
- if (home && home.trim() !== "")
13
- return home;
14
- return path.join(os.homedir(), ".telecrypt-io", "storage");
15
- }
16
- export function sessionPath(dir = profileDir()) {
17
- return path.join(dir, "session.json");
18
- }
19
- export function cryptoSnapshotPath(dir = profileDir()) {
20
- return path.join(dir, "crypto.snapshot");
21
- }
22
- export function ensureProfileDir(dir = profileDir()) {
23
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
24
- }
25
- export function readSession(dir = profileDir()) {
26
- const p = sessionPath(dir);
27
- if (!fs.existsSync(p))
28
- return null;
29
- const raw = fs.readFileSync(p, "utf8");
30
- return JSON.parse(raw);
31
- }
32
- export function writeSession(session, dir = profileDir()) {
33
- ensureProfileDir(dir);
34
- fs.writeFileSync(sessionPath(dir), JSON.stringify(session, null, 2), {
35
- mode: 0o600,
36
- });
37
- }
38
- /** Clears all local state for this profile (session + crypto store). */
39
- export function clearProfile(dir = profileDir()) {
40
- const sp = sessionPath(dir);
41
- const cp = cryptoSnapshotPath(dir);
42
- if (fs.existsSync(sp))
43
- fs.rmSync(sp);
44
- if (fs.existsSync(cp))
45
- fs.rmSync(cp);
46
- }
@@ -1,36 +0,0 @@
1
- import "fake-indexeddb/auto";
2
- import { TeleCryptIOStorage } from "../TeleCryptIOStorage.js";
3
- import { Session } from "./profile.js";
4
- export interface OpenedStorage {
5
- storage: TeleCryptIOStorage;
6
- session: Session;
7
- /** Persists the crypto store back to disk and stops the client. Call this
8
- * in a `finally` block around every command that opens storage, so the
9
- * next CLI invocation sees whatever this one learned (new megolm sessions,
10
- * device keys, etc.) — this is what makes decryption survive across
11
- * separate processes. */
12
- close: () => Promise<void>;
13
- }
14
- /**
15
- * Opens a TeleCryptIOStorage bound to the current profile's session, having first
16
- * restored the crypto store snapshot (if any) from a previous CLI
17
- * invocation. Throws CliError("not logged in") if there is no session.
18
- */
19
- export declare function openStorage(dir?: string): Promise<OpenedStorage>;
20
- /**
21
- * If server-side key backup is active for this account, waits (best-effort,
22
- * bounded) for the SDK's backup engine to report zero sessions remaining to
23
- * upload. This matters specifically because a CLI command is a *short-lived
24
- * process*: the backup engine deliberately fire-and-forgets its upload loop
25
- * with a randomised 0-10s startup jitter (to avoid a multi-device thundering
26
- * herd), so a command that creates a new megolm session (recovery setup,
27
- * or a file upload once recovery is already set up) and then exits
28
- * immediately can easily outrun that loop, leaving the new key silently
29
- * absent from the backup — recoverable-looking but not actually recoverable.
30
- * Never throws: this is a best-effort settle grace period, not a
31
- * correctness gate on the command's primary result.
32
- */
33
- export declare function waitForBackupSettled(storage: TeleCryptIOStorage, timeoutMs?: number): Promise<void>;
34
- /** Used by login/register: builds storage for a brand-new session and
35
- * establishes the initial (empty) crypto store snapshot on disk. */
36
- export declare function initStorageForNewSession(session: Session, dir?: string): Promise<OpenedStorage>;
@@ -1,126 +0,0 @@
1
- import "fake-indexeddb/auto";
2
- import { CryptoEvent } from "matrix-js-sdk/lib/crypto-api/index.js";
3
- import { TeleCryptIOStorage } from "../TeleCryptIOStorage.js";
4
- import { cryptoSnapshotPath, ensureProfileDir, profileDir, readSession, writeSession } from "./profile.js";
5
- import { persistCryptoStore, restoreCryptoStore } from "./cryptoSnapshot.js";
6
- import { CliError } from "./errors.js";
7
- import { buildTokenRefreshFunction } from "../core/oidc.js";
8
- /**
9
- * Builds a TeleCryptIOStorage for the given session — plain password/access-
10
- * token sessions go through `create()`; sessions with an OIDC token set
11
- * (persisted by `storage login --oidc`, see src/cli/oidc.ts) go through
12
- * `createFromOidc()` with a token refresh function wired to persist
13
- * refreshed tokens straight back to this profile's session.json, so a later
14
- * CLI invocation picks up the refreshed access token rather than the
15
- * (possibly now-expired) one this process started with. Needs no OIDC
16
- * discovery / `window` shim here — `oidcTokenEndpoint` was already resolved
17
- * and persisted at login time (see src/cli/oidc.ts).
18
- */
19
- async function buildStorageForSession(session, dir) {
20
- if (!session.refreshToken || !session.oidcClientId || !session.oidcTokenEndpoint) {
21
- return TeleCryptIOStorage.create({
22
- baseUrl: session.homeserver,
23
- userId: session.userId,
24
- accessToken: session.accessToken,
25
- deviceId: session.deviceId,
26
- });
27
- }
28
- const tokenRefreshFunction = buildTokenRefreshFunction(session.oidcTokenEndpoint, session.oidcClientId, async (tokens) => {
29
- writeSession({
30
- ...session,
31
- accessToken: tokens.accessToken,
32
- refreshToken: tokens.refreshToken ?? session.refreshToken,
33
- }, dir);
34
- });
35
- return TeleCryptIOStorage.createFromOidc({
36
- baseUrl: session.homeserver,
37
- userId: session.userId,
38
- accessToken: session.accessToken,
39
- deviceId: session.deviceId,
40
- refreshToken: session.refreshToken,
41
- tokenRefreshFunction,
42
- });
43
- }
44
- /**
45
- * Opens a TeleCryptIOStorage bound to the current profile's session, having first
46
- * restored the crypto store snapshot (if any) from a previous CLI
47
- * invocation. Throws CliError("not logged in") if there is no session.
48
- */
49
- export async function openStorage(dir = profileDir()) {
50
- const session = readSession(dir);
51
- if (!session) {
52
- throw new CliError("not logged in — run `telecrypt-io storage login` first");
53
- }
54
- const snapshotPath = cryptoSnapshotPath(dir);
55
- await restoreCryptoStore(snapshotPath);
56
- const storage = await buildStorageForSession(session, dir);
57
- let closed = false;
58
- const close = async () => {
59
- if (closed)
60
- return;
61
- closed = true;
62
- try {
63
- await persistCryptoStore(snapshotPath);
64
- }
65
- finally {
66
- storage.getClient().stopClient();
67
- }
68
- };
69
- return { storage, session, close };
70
- }
71
- /**
72
- * If server-side key backup is active for this account, waits (best-effort,
73
- * bounded) for the SDK's backup engine to report zero sessions remaining to
74
- * upload. This matters specifically because a CLI command is a *short-lived
75
- * process*: the backup engine deliberately fire-and-forgets its upload loop
76
- * with a randomised 0-10s startup jitter (to avoid a multi-device thundering
77
- * herd), so a command that creates a new megolm session (recovery setup,
78
- * or a file upload once recovery is already set up) and then exits
79
- * immediately can easily outrun that loop, leaving the new key silently
80
- * absent from the backup — recoverable-looking but not actually recoverable.
81
- * Never throws: this is a best-effort settle grace period, not a
82
- * correctness gate on the command's primary result.
83
- */
84
- export async function waitForBackupSettled(storage, timeoutMs = 20000) {
85
- const active = await storage.keys.isRecoverySetup();
86
- if (!active)
87
- return;
88
- const client = storage.getClient();
89
- await new Promise((resolve) => {
90
- let settled = false;
91
- const finish = () => {
92
- if (settled)
93
- return;
94
- settled = true;
95
- client.removeListener(CryptoEvent.KeyBackupSessionsRemaining, onRemaining);
96
- clearTimeout(timer);
97
- resolve();
98
- };
99
- const onRemaining = (remaining) => {
100
- if (remaining === 0)
101
- finish();
102
- };
103
- client.on(CryptoEvent.KeyBackupSessionsRemaining, onRemaining);
104
- const timer = setTimeout(finish, timeoutMs);
105
- });
106
- }
107
- /** Used by login/register: builds storage for a brand-new session and
108
- * establishes the initial (empty) crypto store snapshot on disk. */
109
- export async function initStorageForNewSession(session, dir = profileDir()) {
110
- ensureProfileDir(dir);
111
- const snapshotPath = cryptoSnapshotPath(dir);
112
- const storage = await buildStorageForSession(session, dir);
113
- let closed = false;
114
- const close = async () => {
115
- if (closed)
116
- return;
117
- closed = true;
118
- try {
119
- await persistCryptoStore(snapshotPath);
120
- }
121
- finally {
122
- storage.getClient().stopClient();
123
- }
124
- };
125
- return { storage, session, close };
126
- }