@telecrypt-io/storage 0.9.2 → 0.9.3
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 +38 -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;
|
|
@@ -7,7 +7,7 @@ import { FileTooLargeError, RecoveryAlreadyConfiguredError, RecoveryRestoreError
|
|
|
7
7
|
import { MAX_MEDIA_FILE_BYTES, MAX_MATRIX_IDENTIFIER_BYTES, validateCanonicalMatrixUserId, validateMatrixDeviceId, validateMatrixEventId, validateMatrixRoomId, } from "./core/constants.js";
|
|
8
8
|
import { cancelResponseBody, raceWithAbort, readMediaResponseBody, readResponseBody, ResponseBodyReadError, } from "./core/http.js";
|
|
9
9
|
import { sanitizeDiagnosticText } from "./core/oidc.js";
|
|
10
|
-
import { validateName } from "./core/validation.js";
|
|
10
|
+
import { isStorageTreeRoom, isStorageTreeState, STORAGE_ROOM_ENCRYPTION_ALGORITHM, validateName, } from "./core/validation.js";
|
|
11
11
|
import { ConditionTimeoutError, waitForCondition } from "./core/poll.js";
|
|
12
12
|
import { isTreeDeleted } from "./deletion-markers.js";
|
|
13
13
|
import { DecryptionKeySafe } from "./key-safe.js";
|
|
@@ -484,9 +484,16 @@ export class TeleCryptIOStorage {
|
|
|
484
484
|
return ownerId;
|
|
485
485
|
}
|
|
486
486
|
async sendEncryptedMetadataMessage(roomId, content) {
|
|
487
|
+
await this.refreshRoomState(roomId);
|
|
488
|
+
this.requireEncryptedStorageRoom(roomId);
|
|
487
489
|
const response = await this.client.sendMessage(roomId, content);
|
|
488
490
|
return validateMatrixEventId(response?.event_id, "storage metadata response event ID");
|
|
489
491
|
}
|
|
492
|
+
requireEncryptedStorageRoom(roomId) {
|
|
493
|
+
if (!isStorageTreeRoom(this.client.getRoom(roomId))) {
|
|
494
|
+
throw new StorageError("storage room encryption is unavailable or unsupported");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
490
497
|
async readEncryptedMetadataMessage(roomId, eventId, allowRedacted = false) {
|
|
491
498
|
const rawEvent = await this.client.fetchRoomEvent(roomId, eventId);
|
|
492
499
|
const event = this.client.getEventMapper()(rawEvent);
|
|
@@ -591,7 +598,7 @@ export class TeleCryptIOStorage {
|
|
|
591
598
|
}
|
|
592
599
|
const wire = event.getWireContent();
|
|
593
600
|
if (event.isRedacted() || event.isDecryptionFailure() ||
|
|
594
|
-
wire.algorithm !==
|
|
601
|
+
wire.algorithm !== STORAGE_ROOM_ENCRYPTION_ALGORITHM || typeof wire.session_id !== "string" || !wire.session_id) {
|
|
595
602
|
throw new StorageError("encrypted storage session is unavailable");
|
|
596
603
|
}
|
|
597
604
|
sessionId = wire.session_id;
|
|
@@ -745,6 +752,7 @@ export class TeleCryptIOStorage {
|
|
|
745
752
|
await this.refreshRoomState(treeId, { signal });
|
|
746
753
|
if (signal?.aborted)
|
|
747
754
|
throw new StorageError("operation cancelled");
|
|
755
|
+
this.requireEncryptedStorageRoom(treeId);
|
|
748
756
|
const bytes = encryptedContents instanceof Uint8Array
|
|
749
757
|
? encryptedContents
|
|
750
758
|
: new Uint8Array(encryptedContents);
|
|
@@ -916,8 +924,8 @@ export class TeleCryptIOStorage {
|
|
|
916
924
|
const event = (type, key = "") => state.find((item) => item.type === type && item.state_key === key);
|
|
917
925
|
const create = event(EventType.RoomCreate);
|
|
918
926
|
const purpose = event(UNSTABLE_MSC3088_PURPOSE.name, UNSTABLE_MSC3089_TREE_SUBTYPE.name);
|
|
919
|
-
|
|
920
|
-
|
|
927
|
+
const encryption = event(EventType.RoomEncryption);
|
|
928
|
+
if (!isStorageTreeState(create?.content, purpose?.content, encryption?.content))
|
|
921
929
|
return false;
|
|
922
930
|
const userId = this.client.getUserId();
|
|
923
931
|
if (!userId || create?.sender !== userId ||
|
|
@@ -1559,7 +1567,7 @@ export class TeleCryptIOStorage {
|
|
|
1559
1567
|
{
|
|
1560
1568
|
type: EventType.RoomEncryption,
|
|
1561
1569
|
state_key: "",
|
|
1562
|
-
content: { algorithm:
|
|
1570
|
+
content: { algorithm: STORAGE_ROOM_ENCRYPTION_ALGORITHM },
|
|
1563
1571
|
},
|
|
1564
1572
|
],
|
|
1565
1573
|
};
|
|
@@ -1572,8 +1580,9 @@ export class TeleCryptIOStorage {
|
|
|
1572
1580
|
throw new StorageError("operation cancelled");
|
|
1573
1581
|
try {
|
|
1574
1582
|
const tree = this.client.unstableGetFileTreeSpace(roomId);
|
|
1575
|
-
if (tree)
|
|
1583
|
+
if (tree && isStorageTreeRoom(this.client.getRoom(roomId))) {
|
|
1576
1584
|
return this.decorateTreeSpace(tree);
|
|
1585
|
+
}
|
|
1577
1586
|
}
|
|
1578
1587
|
catch (error) {
|
|
1579
1588
|
lastError = error;
|
|
@@ -1613,13 +1622,29 @@ export class TeleCryptIOStorage {
|
|
|
1613
1622
|
return tree;
|
|
1614
1623
|
this.decoratedTreeSpaces.add(tree);
|
|
1615
1624
|
const originalGetDirectories = tree.getDirectories.bind(tree);
|
|
1625
|
+
const originalGetDirectory = typeof tree.getDirectory === "function"
|
|
1626
|
+
? tree.getDirectory.bind(tree)
|
|
1627
|
+
: undefined;
|
|
1616
1628
|
const originalListFiles = typeof tree.listFiles === "function"
|
|
1617
1629
|
? tree.listFiles.bind(tree)
|
|
1618
1630
|
: () => [];
|
|
1619
1631
|
const originalGetFile = typeof tree.getFile === "function"
|
|
1620
1632
|
? tree.getFile.bind(tree)
|
|
1621
1633
|
: () => null;
|
|
1622
|
-
tree.getDirectories = () => originalGetDirectories()
|
|
1634
|
+
tree.getDirectories = () => originalGetDirectories()
|
|
1635
|
+
.filter((child) => !isTreeDeleted(this.client, child.id) &&
|
|
1636
|
+
isStorageTreeRoom(this.client.getRoom(child.id)))
|
|
1637
|
+
.map((child) => this.decorateTreeSpace(child));
|
|
1638
|
+
if (originalGetDirectory) {
|
|
1639
|
+
tree.getDirectory = (roomId) => {
|
|
1640
|
+
const child = originalGetDirectory(roomId);
|
|
1641
|
+
return child &&
|
|
1642
|
+
!isTreeDeleted(this.client, child.id) &&
|
|
1643
|
+
isStorageTreeRoom(this.client.getRoom(child.id))
|
|
1644
|
+
? this.decorateTreeSpace(child)
|
|
1645
|
+
: undefined;
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1623
1648
|
tree.createDirectory = (name) => this.createSubtree(tree, name);
|
|
1624
1649
|
tree.setName = (name) => this.writeTreeName(tree.id, name);
|
|
1625
1650
|
tree.listFiles = () => originalListFiles().map((branch) => this.decorateFileBranch(tree.id, branch));
|
|
@@ -1782,14 +1807,16 @@ export class TeleCryptIOStorage {
|
|
|
1782
1807
|
throw new StorageError("operation cancelled");
|
|
1783
1808
|
if (isTreeDeleted(this.client, room.roomId))
|
|
1784
1809
|
continue;
|
|
1785
|
-
|
|
1810
|
+
let tree = this.getTree(room.roomId);
|
|
1786
1811
|
if (tree) {
|
|
1787
1812
|
// Existing owner sessions repair the persisted permissions on load;
|
|
1788
1813
|
// readers never attempt this owner-only mutation.
|
|
1789
1814
|
if (this.getStateEvent(room.roomId, EventType.RoomCreate, "")?.getSender() === this.client.getUserId()) {
|
|
1790
1815
|
await this.refreshRoomState(room.roomId, { signal });
|
|
1816
|
+
tree = this.getTree(room.roomId);
|
|
1791
1817
|
}
|
|
1792
|
-
|
|
1818
|
+
if (tree)
|
|
1819
|
+
trees.push(tree);
|
|
1793
1820
|
}
|
|
1794
1821
|
}
|
|
1795
1822
|
return trees;
|
|
@@ -1943,6 +1970,8 @@ export class TeleCryptIOStorage {
|
|
|
1943
1970
|
getTree(roomId) {
|
|
1944
1971
|
if (isTreeDeleted(this.client, roomId))
|
|
1945
1972
|
return null;
|
|
1973
|
+
if (!isStorageTreeRoom(this.client.getRoom(roomId)))
|
|
1974
|
+
return null;
|
|
1946
1975
|
const tree = this.client.unstableGetFileTreeSpace(roomId);
|
|
1947
1976
|
return tree ? this.decorateTreeSpace(tree) : null;
|
|
1948
1977
|
}
|
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.3",
|
|
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",
|