@telecrypt-io/storage 0.9.2 → 0.9.4
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 +3 -0
- package/dist/TeleCryptIOStorage.d.ts +1 -0
- package/dist/TeleCryptIOStorage.js +50 -9
- package/dist/core/oidc.d.ts +3 -3
- package/dist/core/oidc.js +17 -4
- package/dist/core/operations.js +4 -12
- package/dist/core/validation.d.ts +15 -0
- package/dist/core/validation.js +45 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -12,6 +12,9 @@ unmerged Matrix proposal.
|
|
|
12
12
|
npm install @telecrypt-io/storage
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
The SDK supports Node.js `24.21.0` as its exact engine version. Its release tooling uses the
|
|
16
|
+
bundled npm `11.19.0`.
|
|
17
|
+
|
|
15
18
|
## Browser quick start
|
|
16
19
|
|
|
17
20
|
The example below starts with an authenticated OIDC session. Obtain `userId`, `accessToken`, and
|
|
@@ -170,6 +170,7 @@ export declare class TeleCryptIOStorage {
|
|
|
170
170
|
private getStateEvent;
|
|
171
171
|
private getTreeOwnerId;
|
|
172
172
|
private sendEncryptedMetadataMessage;
|
|
173
|
+
private requireEncryptedStorageRoom;
|
|
173
174
|
private readEncryptedMetadataMessage;
|
|
174
175
|
private writeTreeName;
|
|
175
176
|
private getTreeMetadataEvent;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ClientEvent, createClient, EventType, JoinRule, MatrixEvent, MatrixError, MsgType, Preset, RoomCreateTypeField, RoomType, SyncState, UNSTABLE_MSC3088_ENABLED, UNSTABLE_MSC3088_PURPOSE, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF, UNSTABLE_MSC3089_TREE_SUBTYPE, } from "matrix-js-sdk";
|
|
2
|
+
import { EventShieldReason, OnlySignedDevicesIsolationMode, } from "matrix-js-sdk/lib/crypto-api/index.js";
|
|
2
3
|
import { Method } from "matrix-js-sdk/lib/http-api/method.js";
|
|
3
4
|
import { ClientPrefix } from "matrix-js-sdk/lib/http-api/prefix.js";
|
|
4
5
|
import { encryptAttachment, decryptAttachment } from "matrix-encrypt-attachment";
|
|
@@ -7,7 +8,7 @@ import { FileTooLargeError, RecoveryAlreadyConfiguredError, RecoveryRestoreError
|
|
|
7
8
|
import { MAX_MEDIA_FILE_BYTES, MAX_MATRIX_IDENTIFIER_BYTES, validateCanonicalMatrixUserId, validateMatrixDeviceId, validateMatrixEventId, validateMatrixRoomId, } from "./core/constants.js";
|
|
8
9
|
import { cancelResponseBody, raceWithAbort, readMediaResponseBody, readResponseBody, ResponseBodyReadError, } from "./core/http.js";
|
|
9
10
|
import { sanitizeDiagnosticText } from "./core/oidc.js";
|
|
10
|
-
import { validateName } from "./core/validation.js";
|
|
11
|
+
import { isStorageTreeRoom, isStorageTreeState, STORAGE_ROOM_ENCRYPTION_ALGORITHM, validateName, } from "./core/validation.js";
|
|
11
12
|
import { ConditionTimeoutError, waitForCondition } from "./core/poll.js";
|
|
12
13
|
import { isTreeDeleted } from "./deletion-markers.js";
|
|
13
14
|
import { DecryptionKeySafe } from "./key-safe.js";
|
|
@@ -484,9 +485,16 @@ export class TeleCryptIOStorage {
|
|
|
484
485
|
return ownerId;
|
|
485
486
|
}
|
|
486
487
|
async sendEncryptedMetadataMessage(roomId, content) {
|
|
488
|
+
await this.refreshRoomState(roomId);
|
|
489
|
+
this.requireEncryptedStorageRoom(roomId);
|
|
487
490
|
const response = await this.client.sendMessage(roomId, content);
|
|
488
491
|
return validateMatrixEventId(response?.event_id, "storage metadata response event ID");
|
|
489
492
|
}
|
|
493
|
+
requireEncryptedStorageRoom(roomId) {
|
|
494
|
+
if (!isStorageTreeRoom(this.client.getRoom(roomId))) {
|
|
495
|
+
throw new StorageError("storage room encryption is unavailable or unsupported");
|
|
496
|
+
}
|
|
497
|
+
}
|
|
490
498
|
async readEncryptedMetadataMessage(roomId, eventId, allowRedacted = false) {
|
|
491
499
|
const rawEvent = await this.client.fetchRoomEvent(roomId, eventId);
|
|
492
500
|
const event = this.client.getEventMapper()(rawEvent);
|
|
@@ -507,6 +515,13 @@ export class TeleCryptIOStorage {
|
|
|
507
515
|
event.getType() !== EventType.RoomMessage) {
|
|
508
516
|
throw new StorageError("encrypted storage metadata is invalid or unavailable");
|
|
509
517
|
}
|
|
518
|
+
const crypto = this.client.getCrypto();
|
|
519
|
+
if (!crypto)
|
|
520
|
+
throw new StorageError("encryption is not initialized");
|
|
521
|
+
const encryptionInfo = await crypto.getEncryptionInfoForEvent(event);
|
|
522
|
+
if (!encryptionInfo || encryptionInfo.shieldReason === EventShieldReason.MISMATCHED_SENDER) {
|
|
523
|
+
throw new UndecryptableFileError();
|
|
524
|
+
}
|
|
510
525
|
return event;
|
|
511
526
|
}
|
|
512
527
|
async writeTreeName(roomId, name) {
|
|
@@ -591,7 +606,7 @@ export class TeleCryptIOStorage {
|
|
|
591
606
|
}
|
|
592
607
|
const wire = event.getWireContent();
|
|
593
608
|
if (event.isRedacted() || event.isDecryptionFailure() ||
|
|
594
|
-
wire.algorithm !==
|
|
609
|
+
wire.algorithm !== STORAGE_ROOM_ENCRYPTION_ALGORITHM || typeof wire.session_id !== "string" || !wire.session_id) {
|
|
595
610
|
throw new StorageError("encrypted storage session is unavailable");
|
|
596
611
|
}
|
|
597
612
|
sessionId = wire.session_id;
|
|
@@ -745,6 +760,7 @@ export class TeleCryptIOStorage {
|
|
|
745
760
|
await this.refreshRoomState(treeId, { signal });
|
|
746
761
|
if (signal?.aborted)
|
|
747
762
|
throw new StorageError("operation cancelled");
|
|
763
|
+
this.requireEncryptedStorageRoom(treeId);
|
|
748
764
|
const bytes = encryptedContents instanceof Uint8Array
|
|
749
765
|
? encryptedContents
|
|
750
766
|
: new Uint8Array(encryptedContents);
|
|
@@ -916,8 +932,8 @@ export class TeleCryptIOStorage {
|
|
|
916
932
|
const event = (type, key = "") => state.find((item) => item.type === type && item.state_key === key);
|
|
917
933
|
const create = event(EventType.RoomCreate);
|
|
918
934
|
const purpose = event(UNSTABLE_MSC3088_PURPOSE.name, UNSTABLE_MSC3089_TREE_SUBTYPE.name);
|
|
919
|
-
|
|
920
|
-
|
|
935
|
+
const encryption = event(EventType.RoomEncryption);
|
|
936
|
+
if (!isStorageTreeState(create?.content, purpose?.content, encryption?.content))
|
|
921
937
|
return false;
|
|
922
938
|
const userId = this.client.getUserId();
|
|
923
939
|
if (!userId || create?.sender !== userId ||
|
|
@@ -1023,6 +1039,10 @@ export class TeleCryptIOStorage {
|
|
|
1023
1039
|
useIndexedDB: persistent,
|
|
1024
1040
|
cryptoDatabasePrefix: opts.cryptoDatabasePrefix ?? `telecrypt-io-storage::${opts.userId}::${opts.deviceId}`,
|
|
1025
1041
|
}), opts.initTimeoutMs ?? 60000, "crypto init", opts.signal);
|
|
1042
|
+
const crypto = client.getCrypto();
|
|
1043
|
+
if (!crypto)
|
|
1044
|
+
throw new StorageError("encryption is not initialized");
|
|
1045
|
+
crypto.setDeviceIsolationMode(new OnlySignedDevicesIsolationMode());
|
|
1026
1046
|
progress("Encryption ready — opening secure store…");
|
|
1027
1047
|
storage = new TeleCryptIOStorage(client, opts, opts);
|
|
1028
1048
|
if (opts.startClient === false) {
|
|
@@ -1559,7 +1579,7 @@ export class TeleCryptIOStorage {
|
|
|
1559
1579
|
{
|
|
1560
1580
|
type: EventType.RoomEncryption,
|
|
1561
1581
|
state_key: "",
|
|
1562
|
-
content: { algorithm:
|
|
1582
|
+
content: { algorithm: STORAGE_ROOM_ENCRYPTION_ALGORITHM },
|
|
1563
1583
|
},
|
|
1564
1584
|
],
|
|
1565
1585
|
};
|
|
@@ -1572,8 +1592,9 @@ export class TeleCryptIOStorage {
|
|
|
1572
1592
|
throw new StorageError("operation cancelled");
|
|
1573
1593
|
try {
|
|
1574
1594
|
const tree = this.client.unstableGetFileTreeSpace(roomId);
|
|
1575
|
-
if (tree)
|
|
1595
|
+
if (tree && isStorageTreeRoom(this.client.getRoom(roomId))) {
|
|
1576
1596
|
return this.decorateTreeSpace(tree);
|
|
1597
|
+
}
|
|
1577
1598
|
}
|
|
1578
1599
|
catch (error) {
|
|
1579
1600
|
lastError = error;
|
|
@@ -1613,13 +1634,29 @@ export class TeleCryptIOStorage {
|
|
|
1613
1634
|
return tree;
|
|
1614
1635
|
this.decoratedTreeSpaces.add(tree);
|
|
1615
1636
|
const originalGetDirectories = tree.getDirectories.bind(tree);
|
|
1637
|
+
const originalGetDirectory = typeof tree.getDirectory === "function"
|
|
1638
|
+
? tree.getDirectory.bind(tree)
|
|
1639
|
+
: undefined;
|
|
1616
1640
|
const originalListFiles = typeof tree.listFiles === "function"
|
|
1617
1641
|
? tree.listFiles.bind(tree)
|
|
1618
1642
|
: () => [];
|
|
1619
1643
|
const originalGetFile = typeof tree.getFile === "function"
|
|
1620
1644
|
? tree.getFile.bind(tree)
|
|
1621
1645
|
: () => null;
|
|
1622
|
-
tree.getDirectories = () => originalGetDirectories()
|
|
1646
|
+
tree.getDirectories = () => originalGetDirectories()
|
|
1647
|
+
.filter((child) => !isTreeDeleted(this.client, child.id) &&
|
|
1648
|
+
isStorageTreeRoom(this.client.getRoom(child.id)))
|
|
1649
|
+
.map((child) => this.decorateTreeSpace(child));
|
|
1650
|
+
if (originalGetDirectory) {
|
|
1651
|
+
tree.getDirectory = (roomId) => {
|
|
1652
|
+
const child = originalGetDirectory(roomId);
|
|
1653
|
+
return child &&
|
|
1654
|
+
!isTreeDeleted(this.client, child.id) &&
|
|
1655
|
+
isStorageTreeRoom(this.client.getRoom(child.id))
|
|
1656
|
+
? this.decorateTreeSpace(child)
|
|
1657
|
+
: undefined;
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1623
1660
|
tree.createDirectory = (name) => this.createSubtree(tree, name);
|
|
1624
1661
|
tree.setName = (name) => this.writeTreeName(tree.id, name);
|
|
1625
1662
|
tree.listFiles = () => originalListFiles().map((branch) => this.decorateFileBranch(tree.id, branch));
|
|
@@ -1782,14 +1819,16 @@ export class TeleCryptIOStorage {
|
|
|
1782
1819
|
throw new StorageError("operation cancelled");
|
|
1783
1820
|
if (isTreeDeleted(this.client, room.roomId))
|
|
1784
1821
|
continue;
|
|
1785
|
-
|
|
1822
|
+
let tree = this.getTree(room.roomId);
|
|
1786
1823
|
if (tree) {
|
|
1787
1824
|
// Existing owner sessions repair the persisted permissions on load;
|
|
1788
1825
|
// readers never attempt this owner-only mutation.
|
|
1789
1826
|
if (this.getStateEvent(room.roomId, EventType.RoomCreate, "")?.getSender() === this.client.getUserId()) {
|
|
1790
1827
|
await this.refreshRoomState(room.roomId, { signal });
|
|
1828
|
+
tree = this.getTree(room.roomId);
|
|
1791
1829
|
}
|
|
1792
|
-
|
|
1830
|
+
if (tree)
|
|
1831
|
+
trees.push(tree);
|
|
1793
1832
|
}
|
|
1794
1833
|
}
|
|
1795
1834
|
return trees;
|
|
@@ -1943,6 +1982,8 @@ export class TeleCryptIOStorage {
|
|
|
1943
1982
|
getTree(roomId) {
|
|
1944
1983
|
if (isTreeDeleted(this.client, roomId))
|
|
1945
1984
|
return null;
|
|
1985
|
+
if (!isStorageTreeRoom(this.client.getRoom(roomId)))
|
|
1986
|
+
return null;
|
|
1946
1987
|
const tree = this.client.unstableGetFileTreeSpace(roomId);
|
|
1947
1988
|
return tree ? this.decorateTreeSpace(tree) : null;
|
|
1948
1989
|
}
|
package/dist/core/oidc.d.ts
CHANGED
|
@@ -30,9 +30,9 @@ export interface OidcRegistrationClientMetadata {
|
|
|
30
30
|
}
|
|
31
31
|
/** The issuer-bound endpoints accepted by the token refresh adapter. */
|
|
32
32
|
export type OidcTokenEndpointMetadata = Pick<OidcClientConfig, "issuer" | "token_endpoint" | "revocation_endpoint">;
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
/** Redact recognized secret fields and identifiers in provider diagnostics.
|
|
34
|
+
* Structured JSON uses decoded property names at every depth; the text fallback
|
|
35
|
+
* is limited to patterns recognized in unstructured input. */
|
|
36
36
|
export declare function sanitizeDiagnosticText(value: string): string;
|
|
37
37
|
/** Validates an endpoint against the configured homeserver and optional issuer path. */
|
|
38
38
|
export declare function assertOidcEndpoint(value: unknown, trustedHomeserver: string, name: string, issuer?: URL, allowQuery?: boolean): string;
|
package/dist/core/oidc.js
CHANGED
|
@@ -64,10 +64,8 @@ const DIAGNOSTIC_SECRET_FIELD = /(?:access[_-]?token|refresh[_-]?token|id[_-]?to
|
|
|
64
64
|
const DIAGNOSTIC_EMAIL = /[A-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)+/giu;
|
|
65
65
|
const DIAGNOSTIC_MXID = /@[A-Z0-9._=+\-/]+:[A-Z0-9.-]+/giu;
|
|
66
66
|
const DIAGNOSTIC_ULID = /\b[0-9A-HJKMNP-TV-Z]{26}\b/giu;
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
* SDK boundary. */
|
|
70
|
-
export function sanitizeDiagnosticText(value) {
|
|
67
|
+
/** Existing fallback for plain or malformed provider diagnostics. */
|
|
68
|
+
function sanitizeUnstructuredDiagnosticText(value) {
|
|
71
69
|
const quotedValue = `"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|[^\\s,};\\]]+`;
|
|
72
70
|
return value
|
|
73
71
|
.replace(/(Bearer\s+)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s"',}]+)/giu, "$1<redacted>")
|
|
@@ -81,6 +79,21 @@ export function sanitizeDiagnosticText(value) {
|
|
|
81
79
|
.replace(/[\r\n\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu, " ")
|
|
82
80
|
.trim();
|
|
83
81
|
}
|
|
82
|
+
/** Redact recognized secret fields and identifiers in provider diagnostics.
|
|
83
|
+
* Structured JSON uses decoded property names at every depth; the text fallback
|
|
84
|
+
* is limited to patterns recognized in unstructured input. */
|
|
85
|
+
export function sanitizeDiagnosticText(value) {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.stringify(JSON.parse(value), (key, item) => {
|
|
88
|
+
if (DIAGNOSTIC_SECRET_FIELD.test(key))
|
|
89
|
+
return "<redacted>";
|
|
90
|
+
return typeof item === "string" ? sanitizeUnstructuredDiagnosticText(item) : item;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return sanitizeUnstructuredDiagnosticText(value);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
84
97
|
class OidcResponseError extends Error {
|
|
85
98
|
status;
|
|
86
99
|
constructor(operation, status, body, cause) {
|
package/dist/core/operations.js
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* platform-specific and stays with the caller.
|
|
12
12
|
*/
|
|
13
13
|
import { readFileEventMetadata, withMatrixMutationAbort, withTreeMutation, } from "../TeleCryptIOStorage.js";
|
|
14
|
-
import { EventType, MatrixError,
|
|
14
|
+
import { EventType, MatrixError, } from "matrix-js-sdk";
|
|
15
15
|
import { deleteOwnedMedia } from "../media.js";
|
|
16
16
|
import { validateMatrixEventId } from "./constants.js";
|
|
17
17
|
import { FileTooLargeError, MutationPartialError, MutationOutcomeUnknownError, NonEmptyTreeError, RoomCreationAmbiguousError, RoomCleanupIncompleteError, StorageError, } from "./errors.js";
|
|
18
18
|
import { ConditionTimeoutError, waitForCondition } from "./poll.js";
|
|
19
|
-
import { validateName } from "./validation.js";
|
|
19
|
+
import { hasStorageTreeMarkersRoom, isStorageTreeRoom, validateName } from "./validation.js";
|
|
20
20
|
import { prepareReaderHistorySharing } from "../sharing.js";
|
|
21
21
|
import { isFileDeleted, isTreeDeleted, markFileDeleted, markTreeDeleted, } from "../deletion-markers.js";
|
|
22
22
|
/**
|
|
@@ -660,14 +660,6 @@ function hasActiveSpaceParent(room) {
|
|
|
660
660
|
const events = result == null ? [] : Array.isArray(result) ? result : [result];
|
|
661
661
|
return events.some((event) => isActiveRelationEvent(event));
|
|
662
662
|
}
|
|
663
|
-
function isReviewedStorageTreeRoom(room) {
|
|
664
|
-
const createEvent = room?.currentState?.getStateEvents?.(EventType.RoomCreate, "");
|
|
665
|
-
const createContent = createEvent?.getContent?.();
|
|
666
|
-
const purposeEvent = room?.currentState?.getStateEvents?.(UNSTABLE_MSC3088_PURPOSE.name, UNSTABLE_MSC3089_TREE_SUBTYPE.name);
|
|
667
|
-
const purposeContent = purposeEvent?.getContent?.();
|
|
668
|
-
return (createContent?.[RoomCreateTypeField] === RoomType.Space &&
|
|
669
|
-
purposeContent?.[UNSTABLE_MSC3088_ENABLED.name] === true);
|
|
670
|
-
}
|
|
671
663
|
/** Rooms where this account is invited and the room looks like a file tree. */
|
|
672
664
|
export async function listPendingInvites(storage, options) {
|
|
673
665
|
await storage.keySafe.requireReady({ signal: options?.signal });
|
|
@@ -695,7 +687,7 @@ export async function listPendingInvites(storage, options) {
|
|
|
695
687
|
}
|
|
696
688
|
// Invite state may not have MSC3089 tree metadata yet — accept rooms whose
|
|
697
689
|
// create event marks them as a file tree space.
|
|
698
|
-
if (
|
|
690
|
+
if (isStorageTreeRoom(room)) {
|
|
699
691
|
invites.push({
|
|
700
692
|
id: room.roomId,
|
|
701
693
|
name: roomDisplayName(storage, room.roomId),
|
|
@@ -734,7 +726,7 @@ export async function declineInvite(storage, vaultId, options) {
|
|
|
734
726
|
// requiring a joined TreeSpace object.
|
|
735
727
|
if (!currentRoom ||
|
|
736
728
|
hasActiveSpaceParent(currentRoom) ||
|
|
737
|
-
!
|
|
729
|
+
!hasStorageTreeMarkersRoom(currentRoom)) {
|
|
738
730
|
throw new StorageError("decline failed");
|
|
739
731
|
}
|
|
740
732
|
let membership;
|
|
@@ -1,3 +1,18 @@
|
|
|
1
1
|
/** Shared name contract used by SDK inputs and remote projections. */
|
|
2
2
|
export declare function isValidName(value: unknown): value is string;
|
|
3
3
|
export declare function validateName(value: unknown, label: string): asserts value is string;
|
|
4
|
+
export declare const STORAGE_ROOM_ENCRYPTION_ALGORITHM = "m.megolm.v1.aes-sha2";
|
|
5
|
+
interface StorageTreeRoomLike {
|
|
6
|
+
currentState?: {
|
|
7
|
+
getStateEvents?: (eventType: string, stateKey?: string) => unknown;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** The shared Storage-room contract for both synced rooms and refreshed state. */
|
|
11
|
+
export declare function isStorageTreeState(createContent: unknown, purposeContent: unknown, encryptionContent: unknown): boolean;
|
|
12
|
+
/** The room markers needed for explicit invite cleanup before encryption is available. */
|
|
13
|
+
export declare function hasStorageTreeMarkers(createContent: unknown, purposeContent: unknown): boolean;
|
|
14
|
+
/** Checks the existing Storage markers and Megolm room encryption together. */
|
|
15
|
+
export declare function isStorageTreeRoom(room: StorageTreeRoomLike | null | undefined): boolean;
|
|
16
|
+
/** Checks only the room markers for explicit cleanup paths such as declining an invite. */
|
|
17
|
+
export declare function hasStorageTreeMarkersRoom(room: StorageTreeRoomLike | null | undefined): boolean;
|
|
18
|
+
export {};
|
package/dist/core/validation.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventType, RoomCreateTypeField, RoomType, UNSTABLE_MSC3088_ENABLED, UNSTABLE_MSC3088_PURPOSE, UNSTABLE_MSC3089_TREE_SUBTYPE, } from "matrix-js-sdk";
|
|
1
2
|
/** Shared name contract used by SDK inputs and remote projections. */
|
|
2
3
|
export function isValidName(value) {
|
|
3
4
|
return (typeof value === "string" &&
|
|
@@ -9,3 +10,47 @@ export function validateName(value, label) {
|
|
|
9
10
|
throw new Error(`invalid ${label}`);
|
|
10
11
|
}
|
|
11
12
|
}
|
|
13
|
+
export const STORAGE_ROOM_ENCRYPTION_ALGORITHM = "m.megolm.v1.aes-sha2";
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
/** The shared Storage-room contract for both synced rooms and refreshed state. */
|
|
18
|
+
export function isStorageTreeState(createContent, purposeContent, encryptionContent) {
|
|
19
|
+
return hasStorageTreeMarkers(createContent, purposeContent) &&
|
|
20
|
+
isRecord(encryptionContent) &&
|
|
21
|
+
encryptionContent.algorithm === STORAGE_ROOM_ENCRYPTION_ALGORITHM;
|
|
22
|
+
}
|
|
23
|
+
/** The room markers needed for explicit invite cleanup before encryption is available. */
|
|
24
|
+
export function hasStorageTreeMarkers(createContent, purposeContent) {
|
|
25
|
+
return (isRecord(createContent) &&
|
|
26
|
+
createContent[RoomCreateTypeField] === RoomType.Space &&
|
|
27
|
+
isRecord(purposeContent) &&
|
|
28
|
+
purposeContent[UNSTABLE_MSC3088_ENABLED.name] === true);
|
|
29
|
+
}
|
|
30
|
+
function getStorageTreeState(room) {
|
|
31
|
+
const getContent = (eventType, stateKey) => {
|
|
32
|
+
const raw = room?.currentState?.getStateEvents?.(eventType, stateKey);
|
|
33
|
+
const event = Array.isArray(raw) ? raw[0] : raw;
|
|
34
|
+
if (typeof event !== "object" || event === null)
|
|
35
|
+
return undefined;
|
|
36
|
+
const getEventContent = event.getContent;
|
|
37
|
+
return typeof getEventContent === "function"
|
|
38
|
+
? getEventContent.call(event)
|
|
39
|
+
: undefined;
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
createContent: getContent(EventType.RoomCreate, ""),
|
|
43
|
+
purposeContent: getContent(UNSTABLE_MSC3088_PURPOSE.name, UNSTABLE_MSC3089_TREE_SUBTYPE.name),
|
|
44
|
+
encryptionContent: getContent(EventType.RoomEncryption, ""),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Checks the existing Storage markers and Megolm room encryption together. */
|
|
48
|
+
export function isStorageTreeRoom(room) {
|
|
49
|
+
const state = getStorageTreeState(room);
|
|
50
|
+
return isStorageTreeState(state.createContent, state.purposeContent, state.encryptionContent);
|
|
51
|
+
}
|
|
52
|
+
/** Checks only the room markers for explicit cleanup paths such as declining an invite. */
|
|
53
|
+
export function hasStorageTreeMarkersRoom(room) {
|
|
54
|
+
const state = getStorageTreeState(room);
|
|
55
|
+
return hasStorageTreeMarkers(state.createContent, state.purposeContent);
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telecrypt-io/storage",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"license": "BUSL-1.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.19.0",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"engines": {
|
|
25
|
-
"node": "24.
|
|
25
|
+
"node": "24.21.0"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"eslint": "10.9.0",
|
|
49
49
|
"fake-indexeddb": "6.2.5",
|
|
50
50
|
"typescript": "5.9.3",
|
|
51
|
-
"vitest": "4.1.
|
|
51
|
+
"vitest": "4.1.12"
|
|
52
52
|
},
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|