@shhhum/xftp-web 0.1.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 +47 -0
- package/dist/agent.d.ts +46 -0
- package/dist/agent.js +273 -0
- package/dist/agent.js.map +1 -0
- package/dist/client.d.ts +63 -0
- package/dist/client.js +353 -0
- package/dist/client.js.map +1 -0
- package/dist/crypto/digest.d.ts +3 -0
- package/dist/crypto/digest.js +23 -0
- package/dist/crypto/digest.js.map +1 -0
- package/dist/crypto/file.d.ts +14 -0
- package/dist/crypto/file.js +68 -0
- package/dist/crypto/file.js.map +1 -0
- package/dist/crypto/identity.d.ts +10 -0
- package/dist/crypto/identity.js +98 -0
- package/dist/crypto/identity.js.map +1 -0
- package/dist/crypto/keys.d.ts +27 -0
- package/dist/crypto/keys.js +138 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/crypto/padding.d.ts +8 -0
- package/dist/crypto/padding.js +60 -0
- package/dist/crypto/padding.js.map +1 -0
- package/dist/crypto/secretbox.d.ts +22 -0
- package/dist/crypto/secretbox.js +195 -0
- package/dist/crypto/secretbox.js.map +1 -0
- package/dist/download.d.ts +9 -0
- package/dist/download.js +60 -0
- package/dist/download.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/address.d.ts +7 -0
- package/dist/protocol/address.js +50 -0
- package/dist/protocol/address.js.map +1 -0
- package/dist/protocol/chunks.d.ts +15 -0
- package/dist/protocol/chunks.js +75 -0
- package/dist/protocol/chunks.js.map +1 -0
- package/dist/protocol/client.d.ts +9 -0
- package/dist/protocol/client.js +69 -0
- package/dist/protocol/client.js.map +1 -0
- package/dist/protocol/commands.d.ts +68 -0
- package/dist/protocol/commands.js +115 -0
- package/dist/protocol/commands.js.map +1 -0
- package/dist/protocol/description.d.ts +37 -0
- package/dist/protocol/description.js +317 -0
- package/dist/protocol/description.js.map +1 -0
- package/dist/protocol/encoding.d.ts +34 -0
- package/dist/protocol/encoding.js +197 -0
- package/dist/protocol/encoding.js.map +1 -0
- package/dist/protocol/handshake.d.ts +47 -0
- package/dist/protocol/handshake.js +158 -0
- package/dist/protocol/handshake.js.map +1 -0
- package/dist/protocol/transmission.d.ts +15 -0
- package/dist/protocol/transmission.js +84 -0
- package/dist/protocol/transmission.js.map +1 -0
- package/package.json +40 -0
- package/src/agent.ts +372 -0
- package/src/client.ts +448 -0
- package/src/crypto/digest.ts +26 -0
- package/src/crypto/file.ts +94 -0
- package/src/crypto/identity.ts +112 -0
- package/src/crypto/keys.ts +172 -0
- package/src/crypto/padding.ts +61 -0
- package/src/crypto/secretbox.ts +219 -0
- package/src/download.ts +76 -0
- package/src/index.ts +4 -0
- package/src/protocol/address.ts +54 -0
- package/src/protocol/chunks.ts +86 -0
- package/src/protocol/client.ts +95 -0
- package/src/protocol/commands.ts +157 -0
- package/src/protocol/description.ts +363 -0
- package/src/protocol/encoding.ts +224 -0
- package/src/protocol/handshake.ts +220 -0
- package/src/protocol/transmission.ts +113 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// XFTP handshake encoding/decoding -- Simplex.FileTransfer.Transport
|
|
2
|
+
//
|
|
3
|
+
// Handles XFTP client/server handshake messages and version negotiation.
|
|
4
|
+
import { Decoder, concatBytes, encodeWord16, decodeWord16, encodeBytes, decodeBytes, encodeMaybe, decodeLarge, decodeNonEmpty } from "./encoding.js";
|
|
5
|
+
import { sha256 } from "../crypto/digest.js";
|
|
6
|
+
import { decodePubKeyX25519 } from "../crypto/keys.js";
|
|
7
|
+
import { blockPad, blockUnpad, XFTP_BLOCK_SIZE } from "./transmission.js";
|
|
8
|
+
// Encode version range as two big-endian Word16s.
|
|
9
|
+
// Matches Haskell: smpEncode (VRange v1 v2) = smpEncode (v1, v2)
|
|
10
|
+
export function encodeVersionRange(vr) {
|
|
11
|
+
return concatBytes(encodeWord16(vr.minVersion), encodeWord16(vr.maxVersion));
|
|
12
|
+
}
|
|
13
|
+
export function decodeVersionRange(d) {
|
|
14
|
+
const minVersion = decodeWord16(d);
|
|
15
|
+
const maxVersion = decodeWord16(d);
|
|
16
|
+
if (minVersion > maxVersion)
|
|
17
|
+
throw new Error("invalid version range: min > max");
|
|
18
|
+
return { minVersion, maxVersion };
|
|
19
|
+
}
|
|
20
|
+
// Version negotiation: intersection of two version ranges, or null if incompatible.
|
|
21
|
+
// Matches Haskell compatibleVRange.
|
|
22
|
+
export function compatibleVRange(a, b) {
|
|
23
|
+
const min = Math.max(a.minVersion, b.minVersion);
|
|
24
|
+
const max = Math.min(a.maxVersion, b.maxVersion);
|
|
25
|
+
if (min > max)
|
|
26
|
+
return null;
|
|
27
|
+
return { minVersion: min, maxVersion: max };
|
|
28
|
+
}
|
|
29
|
+
// Encode client hello (padded to XFTP_BLOCK_SIZE for web clients).
|
|
30
|
+
// Wire format: smpEncode (Maybe ByteString), padded when webChallenge present
|
|
31
|
+
export function encodeClientHello(hello) {
|
|
32
|
+
const body = encodeMaybe(encodeBytes, hello.webChallenge);
|
|
33
|
+
return hello.webChallenge ? blockPad(body, XFTP_BLOCK_SIZE) : body;
|
|
34
|
+
}
|
|
35
|
+
// Encode and pad client handshake to XFTP_BLOCK_SIZE.
|
|
36
|
+
// Wire format: pad(smpEncode (xftpVersion, keyHash), 16384)
|
|
37
|
+
export function encodeClientHandshake(ch) {
|
|
38
|
+
const body = concatBytes(encodeWord16(ch.xftpVersion), encodeBytes(ch.keyHash));
|
|
39
|
+
return blockPad(body, XFTP_BLOCK_SIZE);
|
|
40
|
+
}
|
|
41
|
+
// Decode padded server handshake block.
|
|
42
|
+
// Wire format: unpad(block) -> (versionRange, sessionId, certChainPubKey, sigBytes)
|
|
43
|
+
// where certChainPubKey = (NonEmpty Large certChain, Large signedKey)
|
|
44
|
+
// sigBytes = ByteString (1-byte len prefix, empty for Nothing)
|
|
45
|
+
// Trailing bytes (Tail) are ignored for forward compatibility.
|
|
46
|
+
export function decodeServerHandshake(block) {
|
|
47
|
+
const raw = blockUnpad(block);
|
|
48
|
+
// Detect error responses (server sends padded error string like "HANDSHAKE")
|
|
49
|
+
if (raw.length < 20) {
|
|
50
|
+
const text = String.fromCharCode(...raw);
|
|
51
|
+
if (/^[A-Z_]+$/.test(text)) {
|
|
52
|
+
console.error('[XFTP] Server handshake error: %s', text);
|
|
53
|
+
throw new Error("Server handshake error: " + text);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const d = new Decoder(raw);
|
|
57
|
+
const xftpVersionRange = decodeVersionRange(d);
|
|
58
|
+
const sessionId = decodeBytes(d);
|
|
59
|
+
// CertChainPubKey: smpEncode (encodeCertChain certChain, SignedObject signedPubKey)
|
|
60
|
+
const certChainDer = decodeNonEmpty(decodeLarge, d);
|
|
61
|
+
const signedKeyDer = decodeLarge(d);
|
|
62
|
+
// webIdentityProof: 1-byte length-prefixed ByteString (empty = Nothing)
|
|
63
|
+
let webIdentityProof = null;
|
|
64
|
+
if (d.remaining() > 0) {
|
|
65
|
+
const sigBytes = decodeBytes(d);
|
|
66
|
+
webIdentityProof = sigBytes.length === 0 ? null : sigBytes;
|
|
67
|
+
}
|
|
68
|
+
// Remaining bytes are Tail (ignored for forward compatibility)
|
|
69
|
+
return { xftpVersionRange, sessionId, certChainDer, signedKeyDer, webIdentityProof };
|
|
70
|
+
}
|
|
71
|
+
export function chainIdCaCerts(certChainDer) {
|
|
72
|
+
switch (certChainDer.length) {
|
|
73
|
+
case 0: return { type: 'empty' };
|
|
74
|
+
case 1: return { type: 'self', cert: certChainDer[0] };
|
|
75
|
+
case 2: return { type: 'valid', leafCert: certChainDer[0], idCert: certChainDer[1], caCert: certChainDer[1] };
|
|
76
|
+
case 3: return { type: 'valid', leafCert: certChainDer[0], idCert: certChainDer[1], caCert: certChainDer[2] };
|
|
77
|
+
case 4: return { type: 'valid', leafCert: certChainDer[0], idCert: certChainDer[1], caCert: certChainDer[3] };
|
|
78
|
+
default: return { type: 'long' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// SHA-256 fingerprint of the identity certificate.
|
|
82
|
+
// For 2-cert chains: idCert = last cert (same as CA).
|
|
83
|
+
// For 3+ cert chains: idCert = second cert (distinct from CA).
|
|
84
|
+
// Matches Haskell: getFingerprint idCert HashSHA256
|
|
85
|
+
export function caFingerprint(certChainDer) {
|
|
86
|
+
const cc = chainIdCaCerts(certChainDer);
|
|
87
|
+
if (cc.type !== 'valid')
|
|
88
|
+
throw new Error("caFingerprint: need valid chain (2-4 certs)");
|
|
89
|
+
return sha256(cc.idCert);
|
|
90
|
+
}
|
|
91
|
+
// Parse ASN.1 DER length (short and long form).
|
|
92
|
+
function derLength(d) {
|
|
93
|
+
const first = d.anyByte();
|
|
94
|
+
if (first < 0x80)
|
|
95
|
+
return first;
|
|
96
|
+
const numBytes = first & 0x7f;
|
|
97
|
+
if (numBytes === 0 || numBytes > 4)
|
|
98
|
+
throw new Error("DER: unsupported length encoding");
|
|
99
|
+
let len = 0;
|
|
100
|
+
for (let i = 0; i < numBytes; i++) {
|
|
101
|
+
len = (len << 8) | d.anyByte();
|
|
102
|
+
}
|
|
103
|
+
return len;
|
|
104
|
+
}
|
|
105
|
+
// Read a complete TLV element, returning the full DER bytes (tag + length + value).
|
|
106
|
+
function derElement(d) {
|
|
107
|
+
const start = d.offset();
|
|
108
|
+
d.anyByte(); // tag
|
|
109
|
+
const len = derLength(d);
|
|
110
|
+
d.take(len); // value
|
|
111
|
+
return d.buf.subarray(start, d.offset());
|
|
112
|
+
}
|
|
113
|
+
// Extract components from a SignedExact X.PubKey DER structure.
|
|
114
|
+
// ASN.1 layout:
|
|
115
|
+
// SEQUENCE {
|
|
116
|
+
// SubjectPublicKeyInfo (SEQUENCE) -- the signed object
|
|
117
|
+
// AlgorithmIdentifier (SEQUENCE) -- signature algorithm
|
|
118
|
+
// BIT STRING -- signature
|
|
119
|
+
// }
|
|
120
|
+
export function extractSignedKey(signedDer) {
|
|
121
|
+
const outer = new Decoder(signedDer);
|
|
122
|
+
const outerTag = outer.anyByte();
|
|
123
|
+
if (outerTag !== 0x30)
|
|
124
|
+
throw new Error("SignedExact: expected SEQUENCE tag 0x30, got 0x" + outerTag.toString(16));
|
|
125
|
+
derLength(outer); // consume total content length
|
|
126
|
+
// First element: SubjectPublicKeyInfo
|
|
127
|
+
const objectDer = derElement(outer);
|
|
128
|
+
// Second element: AlgorithmIdentifier
|
|
129
|
+
const algorithm = derElement(outer);
|
|
130
|
+
// Third element: BIT STRING (signature)
|
|
131
|
+
const sigTag = outer.anyByte();
|
|
132
|
+
if (sigTag !== 0x03)
|
|
133
|
+
throw new Error("SignedExact: expected BIT STRING tag 0x03, got 0x" + sigTag.toString(16));
|
|
134
|
+
const sigLen = derLength(outer);
|
|
135
|
+
const unusedBits = outer.anyByte();
|
|
136
|
+
if (unusedBits !== 0)
|
|
137
|
+
throw new Error("SignedExact: expected 0 unused bits in signature");
|
|
138
|
+
const signature = outer.take(sigLen - 1);
|
|
139
|
+
// Extract X25519 key from the signed object.
|
|
140
|
+
// objectDer may be the raw SPKI (44 bytes) or a wrapper SEQUENCE
|
|
141
|
+
// from x509 objectToSignedExact which wraps toASN1 in Start Sequence.
|
|
142
|
+
const dhKey = decodeX25519Key(objectDer);
|
|
143
|
+
return { objectDer, dhKey, algorithm, signature };
|
|
144
|
+
}
|
|
145
|
+
// Extract X25519 raw public key from either direct SPKI (44 bytes)
|
|
146
|
+
// or a wrapper SEQUENCE containing the SPKI.
|
|
147
|
+
function decodeX25519Key(der) {
|
|
148
|
+
if (der.length === 44)
|
|
149
|
+
return decodePubKeyX25519(der);
|
|
150
|
+
if (der[0] !== 0x30)
|
|
151
|
+
throw new Error("decodeX25519Key: expected SEQUENCE");
|
|
152
|
+
const d = new Decoder(der);
|
|
153
|
+
d.anyByte();
|
|
154
|
+
derLength(d);
|
|
155
|
+
const inner = derElement(d);
|
|
156
|
+
return decodePubKeyX25519(inner);
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=handshake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/protocol/handshake.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const XFTP_BLOCK_SIZE = 16384;
|
|
2
|
+
export declare const initialXFTPVersion = 1;
|
|
3
|
+
export declare const authCmdsXFTPVersion = 2;
|
|
4
|
+
export declare const blockedFilesXFTPVersion = 3;
|
|
5
|
+
export declare const currentXFTPVersion = 3;
|
|
6
|
+
export declare function blockPad(msg: Uint8Array, blockSize?: number): Uint8Array;
|
|
7
|
+
export declare function blockUnpad(block: Uint8Array): Uint8Array;
|
|
8
|
+
export declare function encodeAuthTransmission(sessionId: Uint8Array, corrId: Uint8Array, entityId: Uint8Array, cmdBytes: Uint8Array, privateKey: Uint8Array): Uint8Array;
|
|
9
|
+
export declare function encodeTransmission(sessionId: Uint8Array, corrId: Uint8Array, entityId: Uint8Array, cmdBytes: Uint8Array): Uint8Array;
|
|
10
|
+
export interface DecodedTransmission {
|
|
11
|
+
corrId: Uint8Array;
|
|
12
|
+
entityId: Uint8Array;
|
|
13
|
+
command: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
export declare function decodeTransmission(sessionId: Uint8Array, block: Uint8Array): DecodedTransmission;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// XFTP transmission framing -- Simplex.Messaging.Transport + FileTransfer.Protocol
|
|
2
|
+
//
|
|
3
|
+
// Handles block-level pad/unpad, batch encoding, and Ed25519 auth signing.
|
|
4
|
+
import { Decoder, concatBytes, encodeBytes, decodeBytes, encodeLarge, decodeLarge } from "./encoding.js";
|
|
5
|
+
import { sign } from "../crypto/keys.js";
|
|
6
|
+
// -- Constants
|
|
7
|
+
export const XFTP_BLOCK_SIZE = 16384;
|
|
8
|
+
// Protocol versions (FileTransfer.Transport)
|
|
9
|
+
export const initialXFTPVersion = 1;
|
|
10
|
+
export const authCmdsXFTPVersion = 2;
|
|
11
|
+
export const blockedFilesXFTPVersion = 3;
|
|
12
|
+
export const currentXFTPVersion = 3;
|
|
13
|
+
// -- Block-level pad/unpad (Crypto.hs:pad/unPad, strict ByteString)
|
|
14
|
+
export function blockPad(msg, blockSize = XFTP_BLOCK_SIZE) {
|
|
15
|
+
const len = msg.length;
|
|
16
|
+
const padLen = blockSize - len - 2;
|
|
17
|
+
if (padLen < 0)
|
|
18
|
+
throw new Error("blockPad: message too large for block");
|
|
19
|
+
const result = new Uint8Array(blockSize);
|
|
20
|
+
result[0] = (len >>> 8) & 0xff;
|
|
21
|
+
result[1] = len & 0xff;
|
|
22
|
+
result.set(msg, 2);
|
|
23
|
+
result.fill(0x23, 2 + len); // '#' padding
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
export function blockUnpad(block) {
|
|
27
|
+
if (block.length < 2)
|
|
28
|
+
throw new Error("blockUnpad: too short");
|
|
29
|
+
const len = (block[0] << 8) | block[1];
|
|
30
|
+
if (2 + len > block.length)
|
|
31
|
+
throw new Error("blockUnpad: invalid length");
|
|
32
|
+
return block.subarray(2, 2 + len);
|
|
33
|
+
}
|
|
34
|
+
// -- Transmission encoding (client -> server)
|
|
35
|
+
// Encode an authenticated XFTP command as a padded block.
|
|
36
|
+
// Matches xftpEncodeAuthTransmission with implySessId = False:
|
|
37
|
+
// sessionId is included in both signed data AND wire data.
|
|
38
|
+
export function encodeAuthTransmission(sessionId, corrId, entityId, cmdBytes, privateKey) {
|
|
39
|
+
// t' = encodeTransmission_ v t = smpEncode (corrId, entityId) <> cmdBytes
|
|
40
|
+
const tInner = concatBytes(encodeBytes(corrId), encodeBytes(entityId), cmdBytes);
|
|
41
|
+
// tForAuth = smpEncode sessionId <> t'
|
|
42
|
+
const tForAuth = concatBytes(encodeBytes(sessionId), tInner);
|
|
43
|
+
const signature = sign(privateKey, tForAuth);
|
|
44
|
+
const authenticator = encodeBytes(signature);
|
|
45
|
+
// implySessId = False: tToSend = tForAuth (sessionId on wire)
|
|
46
|
+
const encoded = concatBytes(authenticator, tForAuth);
|
|
47
|
+
const batch = concatBytes(new Uint8Array([1]), encodeLarge(encoded));
|
|
48
|
+
return blockPad(batch);
|
|
49
|
+
}
|
|
50
|
+
// Encode an unsigned XFTP command (e.g. PING) as a padded block.
|
|
51
|
+
// Matches xftpEncodeTransmission with implySessId = False: sessionId on wire.
|
|
52
|
+
export function encodeTransmission(sessionId, corrId, entityId, cmdBytes) {
|
|
53
|
+
const tInner = concatBytes(encodeBytes(sessionId), encodeBytes(corrId), encodeBytes(entityId), cmdBytes);
|
|
54
|
+
// No auth: tEncodeAuth False Nothing = smpEncode B.empty = \x00
|
|
55
|
+
const authenticator = encodeBytes(new Uint8Array(0));
|
|
56
|
+
const encoded = concatBytes(authenticator, tInner);
|
|
57
|
+
const batch = concatBytes(new Uint8Array([1]), encodeLarge(encoded));
|
|
58
|
+
return blockPad(batch);
|
|
59
|
+
}
|
|
60
|
+
// Decode a server response block into raw parts.
|
|
61
|
+
// Call decodeResponse(command) from commands.ts to parse the response.
|
|
62
|
+
// Matches xftpDecodeTClient with implySessId = False: reads and verifies sessionId from wire.
|
|
63
|
+
export function decodeTransmission(sessionId, block) {
|
|
64
|
+
const raw = blockUnpad(block);
|
|
65
|
+
const d = new Decoder(raw);
|
|
66
|
+
const count = d.anyByte();
|
|
67
|
+
if (count !== 1)
|
|
68
|
+
throw new Error("decodeTransmission: expected batch count 1, got " + count);
|
|
69
|
+
const transmission = decodeLarge(d);
|
|
70
|
+
const td = new Decoder(transmission);
|
|
71
|
+
// Skip authenticator (server responses have empty auth)
|
|
72
|
+
decodeBytes(td);
|
|
73
|
+
// implySessId = False: read sessionId from wire and verify
|
|
74
|
+
const sessId = decodeBytes(td);
|
|
75
|
+
if (sessId.length !== sessionId.length || !sessId.every((b, i) => b === sessionId[i])) {
|
|
76
|
+
console.error('[XFTP] Session ID mismatch in server response');
|
|
77
|
+
throw new Error("Session ID mismatch in server response");
|
|
78
|
+
}
|
|
79
|
+
const corrId = decodeBytes(td);
|
|
80
|
+
const entityId = decodeBytes(td);
|
|
81
|
+
const command = td.takeAll();
|
|
82
|
+
return { corrId, entityId, command };
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=transmission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transmission.js","sourceRoot":"","sources":["../../src/protocol/transmission.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shhhum/xftp-web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": ["dist", "src"],
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"postinstall": "ln -sf ../../../libsodium-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs node_modules/libsodium-wrappers-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs && npx playwright install chromium",
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"dev": "npx tsx test/runSetup.ts && vite --mode development",
|
|
16
|
+
"build:local": "npx tsx test/runSetup.ts && vite build --mode development",
|
|
17
|
+
"build:prod": "vite build --mode production",
|
|
18
|
+
"preview": "vite preview",
|
|
19
|
+
"preview:local": "npm run build:local && vite preview",
|
|
20
|
+
"preview:prod": "vite build --mode production && vite preview",
|
|
21
|
+
"check:web": "tsc -p tsconfig.web.json --noEmit && tsc -p tsconfig.worker.json --noEmit",
|
|
22
|
+
"test:page": "playwright test test/page.spec.ts"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/libsodium-wrappers-sumo": "^0.7.8",
|
|
26
|
+
"@types/node": "^20.0.0",
|
|
27
|
+
"@types/pako": "^2.0.3",
|
|
28
|
+
"@vitest/browser": "^3.0.0",
|
|
29
|
+
"@playwright/test": "^1.50.0",
|
|
30
|
+
"playwright": "^1.50.0",
|
|
31
|
+
"typescript": "^5.4.0",
|
|
32
|
+
"vite": "^6.0.0",
|
|
33
|
+
"vitest": "^3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@noble/curves": "^1.9.7",
|
|
37
|
+
"libsodium-wrappers-sumo": "^0.7.13",
|
|
38
|
+
"pako": "^2.1.0"
|
|
39
|
+
}
|
|
40
|
+
}
|