@haven-chat-org/core 1.0.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/LICENSE +661 -0
- package/README.md +71 -0
- package/dist/crypto/backup.d.ts +87 -0
- package/dist/crypto/backup.js +62 -0
- package/dist/crypto/backup.js.map +1 -0
- package/dist/crypto/double-ratchet.d.ts +104 -0
- package/dist/crypto/double-ratchet.js +274 -0
- package/dist/crypto/double-ratchet.js.map +1 -0
- package/dist/crypto/file.d.ts +14 -0
- package/dist/crypto/file.js +20 -0
- package/dist/crypto/file.js.map +1 -0
- package/dist/crypto/index.d.ts +9 -0
- package/dist/crypto/index.js +10 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/keys.d.ts +61 -0
- package/dist/crypto/keys.js +79 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/crypto/passphrase.d.ts +10 -0
- package/dist/crypto/passphrase.js +142 -0
- package/dist/crypto/passphrase.js.map +1 -0
- package/dist/crypto/profile.d.ts +31 -0
- package/dist/crypto/profile.js +73 -0
- package/dist/crypto/profile.js.map +1 -0
- package/dist/crypto/sender-keys.d.ts +76 -0
- package/dist/crypto/sender-keys.js +170 -0
- package/dist/crypto/sender-keys.js.map +1 -0
- package/dist/crypto/sender-keys.test.d.ts +1 -0
- package/dist/crypto/sender-keys.test.js +272 -0
- package/dist/crypto/sender-keys.test.js.map +1 -0
- package/dist/crypto/utils.d.ts +41 -0
- package/dist/crypto/utils.js +102 -0
- package/dist/crypto/utils.js.map +1 -0
- package/dist/crypto/x3dh.d.ts +45 -0
- package/dist/crypto/x3dh.js +106 -0
- package/dist/crypto/x3dh.js.map +1 -0
- package/dist/export/__tests__/archive.test.d.ts +1 -0
- package/dist/export/__tests__/archive.test.js +276 -0
- package/dist/export/__tests__/archive.test.js.map +1 -0
- package/dist/export/archive.d.ts +38 -0
- package/dist/export/archive.js +107 -0
- package/dist/export/archive.js.map +1 -0
- package/dist/export/index.d.ts +4 -0
- package/dist/export/index.js +4 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/reader.d.ts +27 -0
- package/dist/export/reader.js +101 -0
- package/dist/export/reader.js.map +1 -0
- package/dist/export/signing.d.ts +15 -0
- package/dist/export/signing.js +44 -0
- package/dist/export/signing.js.map +1 -0
- package/dist/export/types.d.ts +128 -0
- package/dist/export/types.js +3 -0
- package/dist/export/types.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/net/api.d.ts +200 -0
- package/dist/net/api.js +715 -0
- package/dist/net/api.js.map +1 -0
- package/dist/net/api.test.d.ts +1 -0
- package/dist/net/api.test.js +884 -0
- package/dist/net/api.test.js.map +1 -0
- package/dist/net/index.d.ts +2 -0
- package/dist/net/index.js +3 -0
- package/dist/net/index.js.map +1 -0
- package/dist/net/ws.d.ts +71 -0
- package/dist/net/ws.js +257 -0
- package/dist/net/ws.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/memory.d.ts +24 -0
- package/dist/store/memory.js +50 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/types.d.ts +23 -0
- package/dist/store/types.js +2 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types.d.ts +850 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { unzipSync } from "fflate";
|
|
2
|
+
import { fromBase64 } from "../crypto/utils.js";
|
|
3
|
+
import { verifyManifest, computeFileHash } from "./signing.js";
|
|
4
|
+
/**
|
|
5
|
+
* Reads and verifies a .haven archive (ZIP).
|
|
6
|
+
*/
|
|
7
|
+
export class HavenArchiveReader {
|
|
8
|
+
files;
|
|
9
|
+
manifest;
|
|
10
|
+
constructor(files, manifest) {
|
|
11
|
+
this.files = files;
|
|
12
|
+
this.manifest = manifest;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse a .haven archive from raw ZIP bytes.
|
|
16
|
+
*/
|
|
17
|
+
static async fromBlob(data) {
|
|
18
|
+
const files = unzipSync(data);
|
|
19
|
+
const manifestData = files["manifest.json"];
|
|
20
|
+
if (!manifestData) {
|
|
21
|
+
throw new Error("Invalid .haven archive: missing manifest.json");
|
|
22
|
+
}
|
|
23
|
+
const manifest = JSON.parse(new TextDecoder().decode(manifestData));
|
|
24
|
+
return new HavenArchiveReader(files, manifest);
|
|
25
|
+
}
|
|
26
|
+
getManifest() {
|
|
27
|
+
return this.manifest;
|
|
28
|
+
}
|
|
29
|
+
getChannelExport(channelName) {
|
|
30
|
+
// Try channels/ first, then dms/
|
|
31
|
+
const key = `channels/${channelName}.json`;
|
|
32
|
+
const dmKey = `dms/${channelName}.json`;
|
|
33
|
+
const data = this.files[key] ?? this.files[dmKey];
|
|
34
|
+
if (!data)
|
|
35
|
+
return null;
|
|
36
|
+
return JSON.parse(new TextDecoder().decode(data));
|
|
37
|
+
}
|
|
38
|
+
/** Return all channel exports (channels/ and dms/ directories). */
|
|
39
|
+
getChannelExports() {
|
|
40
|
+
const results = [];
|
|
41
|
+
const decoder = new TextDecoder();
|
|
42
|
+
for (const [path, data] of Object.entries(this.files)) {
|
|
43
|
+
if ((path.startsWith("channels/") || path.startsWith("dms/")) && path.endsWith(".json")) {
|
|
44
|
+
try {
|
|
45
|
+
results.push(JSON.parse(decoder.decode(data)));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Skip malformed channel files
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
getServerMeta() {
|
|
55
|
+
const data = this.files["server.json"];
|
|
56
|
+
if (!data)
|
|
57
|
+
return null;
|
|
58
|
+
return JSON.parse(new TextDecoder().decode(data));
|
|
59
|
+
}
|
|
60
|
+
getAttachment(fileRef) {
|
|
61
|
+
return this.files[fileRef] ?? null;
|
|
62
|
+
}
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
getAuditLog() {
|
|
65
|
+
const data = this.files["audit-log.json"];
|
|
66
|
+
if (!data)
|
|
67
|
+
return null;
|
|
68
|
+
return JSON.parse(new TextDecoder().decode(data));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Verify archive integrity: file hashes and optional Ed25519 signature.
|
|
72
|
+
*/
|
|
73
|
+
async verify() {
|
|
74
|
+
const issues = [];
|
|
75
|
+
// Check all files listed in manifest exist and match hashes
|
|
76
|
+
for (const [path, expected] of Object.entries(this.manifest.files)) {
|
|
77
|
+
const fileData = this.files[path];
|
|
78
|
+
if (!fileData) {
|
|
79
|
+
issues.push(`Missing file: ${path}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const actualHash = await computeFileHash(fileData);
|
|
83
|
+
if (actualHash !== expected.sha256) {
|
|
84
|
+
issues.push(`Hash mismatch for ${path}: expected ${expected.sha256}, got ${actualHash}`);
|
|
85
|
+
}
|
|
86
|
+
if (fileData.byteLength !== expected.size) {
|
|
87
|
+
issues.push(`Size mismatch for ${path}: expected ${expected.size}, got ${fileData.byteLength}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Verify Ed25519 signature if present
|
|
91
|
+
if (this.manifest.user_signature) {
|
|
92
|
+
const publicKey = fromBase64(this.manifest.exported_by.identity_key);
|
|
93
|
+
const valid = verifyManifest(this.manifest, this.manifest.user_signature, publicKey);
|
|
94
|
+
if (!valid) {
|
|
95
|
+
issues.push("User signature verification failed");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { valid: issues.length === 0, issues };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../../src/export/reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMhD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/D;;GAEG;AACH,MAAM,OAAO,kBAAkB;IACrB,KAAK,CAA6B;IAClC,QAAQ,CAAgB;IAEhC,YAAoB,KAAiC,EAAE,QAAuB;QAC5E,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAgB;QACpC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,YAAY,GAAG,KAAK,CAAC,eAAe,CAAC,CAAC;QAC5C,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,QAAQ,GAAkB,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;QACnF,OAAO,IAAI,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,gBAAgB,CAAC,WAAmB;QAClC,iCAAiC;QACjC,MAAM,GAAG,GAAG,YAAY,WAAW,OAAO,CAAC;QAC3C,MAAM,KAAK,GAAG,OAAO,WAAW,OAAO,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,mEAAmE;IACnE,iBAAiB;QACf,MAAM,OAAO,GAAyB,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxF,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACjD,CAAC;gBAAC,MAAM,CAAC;oBACP,+BAA+B;gBACjC,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,aAAa;QACX,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,aAAa,CAAC,OAAe;QAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACrC,CAAC;IAED,8DAA8D;IAC9D,WAAW;QACT,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM;QACV,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,4DAA4D;QAC5D,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;gBACrC,SAAS;YACX,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;YACnD,IAAI,UAAU,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACnC,MAAM,CAAC,IAAI,CAAC,qBAAqB,IAAI,cAAc,QAAQ,CAAC,MAAM,SAAS,UAAU,EAAE,CAAC,CAAC;YAC3F,CAAC;YACD,IAAI,QAAQ,CAAC,UAAU,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAC1C,MAAM,CAAC,IAAI,CAAC,qBAAqB,IAAI,cAAc,QAAQ,CAAC,IAAI,SAAS,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YAClG,CAAC;QACH,CAAC;QAED,sCAAsC;QACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;YACrE,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;YACrF,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAChD,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HavenManifest } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sign a manifest with an Ed25519 private key.
|
|
4
|
+
* Returns a base64-encoded detached signature.
|
|
5
|
+
*/
|
|
6
|
+
export declare function signManifest(manifest: HavenManifest, privateKey: Uint8Array): string;
|
|
7
|
+
/**
|
|
8
|
+
* Verify an Ed25519 signature over a manifest.
|
|
9
|
+
*/
|
|
10
|
+
export declare function verifyManifest(manifest: HavenManifest, signature: string, publicKey: Uint8Array): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Compute SHA-256 hex digest of arbitrary data.
|
|
13
|
+
* Uses libsodium for Node.js + browser compatibility.
|
|
14
|
+
*/
|
|
15
|
+
export declare function computeFileHash(data: Uint8Array): Promise<string>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getSodium, toBase64, fromBase64 } from "../crypto/utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Produce a canonical JSON representation of the manifest for signing.
|
|
4
|
+
* Excludes signature fields and uses sorted keys for determinism.
|
|
5
|
+
*/
|
|
6
|
+
function canonicalManifest(manifest) {
|
|
7
|
+
const { user_signature: _u, server_signature: _s, ...rest } = manifest;
|
|
8
|
+
return JSON.stringify(rest, Object.keys(rest).sort());
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sign a manifest with an Ed25519 private key.
|
|
12
|
+
* Returns a base64-encoded detached signature.
|
|
13
|
+
*/
|
|
14
|
+
export function signManifest(manifest, privateKey) {
|
|
15
|
+
const sodium = getSodium();
|
|
16
|
+
const message = new TextEncoder().encode(canonicalManifest(manifest));
|
|
17
|
+
const signature = sodium.crypto_sign_detached(message, privateKey);
|
|
18
|
+
return toBase64(signature);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify an Ed25519 signature over a manifest.
|
|
22
|
+
*/
|
|
23
|
+
export function verifyManifest(manifest, signature, publicKey) {
|
|
24
|
+
const sodium = getSodium();
|
|
25
|
+
const message = new TextEncoder().encode(canonicalManifest(manifest));
|
|
26
|
+
try {
|
|
27
|
+
return sodium.crypto_sign_verify_detached(fromBase64(signature), message, publicKey);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Compute SHA-256 hex digest of arbitrary data.
|
|
35
|
+
* Uses libsodium for Node.js + browser compatibility.
|
|
36
|
+
*/
|
|
37
|
+
export async function computeFileHash(data) {
|
|
38
|
+
const sodium = getSodium();
|
|
39
|
+
const hash = sodium.crypto_hash_sha256(data);
|
|
40
|
+
return Array.from(hash)
|
|
41
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
42
|
+
.join("");
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../../src/export/signing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrE;;;GAGG;AACH,SAAS,iBAAiB,CAAC,QAAuB;IAChD,MAAM,EAAE,cAAc,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IACvE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAAuB,EAAE,UAAsB;IAC1E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,MAAM,CAAC,oBAAoB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnE,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAuB,EACvB,SAAiB,EACjB,SAAqB;IAErB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,2BAA2B,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IACvF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAgB;IACpD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,IAAkB,CAAC;SAClC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SACnD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** Top-level manifest included in every .haven archive. */
|
|
2
|
+
export interface HavenManifest {
|
|
3
|
+
version: number;
|
|
4
|
+
format: "haven-export";
|
|
5
|
+
exported_by: {
|
|
6
|
+
user_id: string;
|
|
7
|
+
username: string;
|
|
8
|
+
identity_key: string;
|
|
9
|
+
};
|
|
10
|
+
exported_at: string;
|
|
11
|
+
scope?: "server" | "channel" | "dm";
|
|
12
|
+
server_id?: string;
|
|
13
|
+
channel_id?: string;
|
|
14
|
+
instance_url: string;
|
|
15
|
+
files: Record<string, {
|
|
16
|
+
sha256: string;
|
|
17
|
+
size: number;
|
|
18
|
+
}>;
|
|
19
|
+
message_count: number;
|
|
20
|
+
date_range: {
|
|
21
|
+
from: string;
|
|
22
|
+
to: string;
|
|
23
|
+
};
|
|
24
|
+
user_signature?: string;
|
|
25
|
+
server_signature?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Per-channel export data stored in channels/<name>.json or dms/<name>.json. */
|
|
28
|
+
export interface HavenChannelExport {
|
|
29
|
+
channel: {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
type: string;
|
|
33
|
+
encrypted: boolean;
|
|
34
|
+
category?: string;
|
|
35
|
+
created_at: string;
|
|
36
|
+
};
|
|
37
|
+
exported_at: string;
|
|
38
|
+
exported_by: string;
|
|
39
|
+
message_count: number;
|
|
40
|
+
date_range: {
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
};
|
|
44
|
+
messages: HavenExportMessage[];
|
|
45
|
+
}
|
|
46
|
+
/** A single message in the export. */
|
|
47
|
+
export interface HavenExportMessage {
|
|
48
|
+
id: string;
|
|
49
|
+
sender_id: string;
|
|
50
|
+
sender_name: string;
|
|
51
|
+
sender_display_name: string | null;
|
|
52
|
+
timestamp: string;
|
|
53
|
+
text: string | null;
|
|
54
|
+
content_type: string;
|
|
55
|
+
formatting: string | null;
|
|
56
|
+
edited: boolean;
|
|
57
|
+
reply_to: string | null;
|
|
58
|
+
type: string;
|
|
59
|
+
reactions: {
|
|
60
|
+
emoji: string;
|
|
61
|
+
count: number;
|
|
62
|
+
users: string[];
|
|
63
|
+
}[];
|
|
64
|
+
pinned: boolean;
|
|
65
|
+
attachments: HavenAttachmentRef[];
|
|
66
|
+
}
|
|
67
|
+
/** Reference to an attachment file within the archive. */
|
|
68
|
+
export interface HavenAttachmentRef {
|
|
69
|
+
id: string;
|
|
70
|
+
filename: string;
|
|
71
|
+
mime_type: string;
|
|
72
|
+
size: number;
|
|
73
|
+
width?: number;
|
|
74
|
+
height?: number;
|
|
75
|
+
file_ref: string;
|
|
76
|
+
}
|
|
77
|
+
/** Server-level metadata export stored in server.json. */
|
|
78
|
+
export interface HavenServerExport {
|
|
79
|
+
server: {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
description: string | null;
|
|
83
|
+
icon_url: string | null;
|
|
84
|
+
created_at: string;
|
|
85
|
+
};
|
|
86
|
+
categories: {
|
|
87
|
+
id: string;
|
|
88
|
+
name: string;
|
|
89
|
+
position: number;
|
|
90
|
+
}[];
|
|
91
|
+
channels: {
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
type: string;
|
|
95
|
+
category_id: string | null;
|
|
96
|
+
position: number;
|
|
97
|
+
encrypted: boolean;
|
|
98
|
+
is_private: boolean;
|
|
99
|
+
}[];
|
|
100
|
+
roles: {
|
|
101
|
+
id: string;
|
|
102
|
+
name: string;
|
|
103
|
+
color: string | null;
|
|
104
|
+
permissions: number;
|
|
105
|
+
position: number;
|
|
106
|
+
is_default: boolean;
|
|
107
|
+
}[];
|
|
108
|
+
members: {
|
|
109
|
+
user_id: string;
|
|
110
|
+
username: string;
|
|
111
|
+
display_name: string | null;
|
|
112
|
+
nickname: string | null;
|
|
113
|
+
roles: string[];
|
|
114
|
+
joined_at: string;
|
|
115
|
+
}[];
|
|
116
|
+
emojis: {
|
|
117
|
+
id: string;
|
|
118
|
+
name: string;
|
|
119
|
+
image_ref?: string;
|
|
120
|
+
}[];
|
|
121
|
+
permission_overwrites: {
|
|
122
|
+
channel_id: string;
|
|
123
|
+
target_type: string;
|
|
124
|
+
target_id: string;
|
|
125
|
+
allow: number;
|
|
126
|
+
deny: number;
|
|
127
|
+
}[];
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/export/types.ts"],"names":[],"mappings":"AAAA,2DAA2D"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { RegisterInput, LoginRequest, AuthResponse, LoginResponse, TotpSetupResponse, TotpVerifyRequest, KeyBundle, UploadPreKeysRequest, PreKeyCountResponse, CreateServerRequest, ServerResponse, CreateChannelRequest, ChannelResponse, CreateDmRequest, SendMessageRequest, MessageResponse, MessageQuery, UploadResponse, CreateInviteRequest, InviteResponse, ServerMemberResponse, DistributeSenderKeyRequest, SenderKeyDistributionResponse, ChannelMemberKeyInfo, PresenceEntry, UpdateKeysRequest, ReactionGroup, UserProfileResponse, UpdateProfileRequest, BlockedUserResponse, CategoryResponse, CreateCategoryRequest, UpdateCategoryRequest, ReorderCategoriesRequest, ReorderChannelsRequest, SetChannelCategoryRequest, RoleResponse, CreateRoleRequest, UpdateRoleRequest, AssignRoleRequest, OverwriteResponse, SetOverwriteRequest, FriendResponse, FriendRequestBody, DmRequestAction, UpdateDmPrivacyRequest, ChangePasswordRequest, CreateGroupDmRequest, ChannelMemberInfo, BanResponse, CreateBanRequest, CreateReportRequest, ReportResponse, VoiceTokenResponse, VoiceParticipant, UploadKeyBackupRequest, KeyBackupResponse, KeyBackupStatusResponse, CustomEmojiResponse, PowChallengeResponse, AuditLogEntry, ReadStateResponse, ChannelUnreadInfo, AdminStats, AdminUserResponse, InviteRequiredResponse, RegistrationInviteResponse, GifSearchResponse, SessionResponse, RestoreServerResponse, ImportMessage } from "../types.js";
|
|
2
|
+
export interface ApiClientOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
onTokenExpired?: () => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Type-safe REST client for the Haven backend.
|
|
8
|
+
* Handles JWT token management and automatic refresh.
|
|
9
|
+
*/
|
|
10
|
+
export declare class HavenApi {
|
|
11
|
+
private baseUrl;
|
|
12
|
+
private accessToken;
|
|
13
|
+
private refreshToken;
|
|
14
|
+
private onTokenExpired?;
|
|
15
|
+
constructor(options: ApiClientOptions);
|
|
16
|
+
/** Set auth tokens (after login/register/refresh). */
|
|
17
|
+
setTokens(access: string, refresh: string): void;
|
|
18
|
+
clearTokens(): void;
|
|
19
|
+
get currentAccessToken(): string | null;
|
|
20
|
+
/** Fetch a PoW challenge from the server. */
|
|
21
|
+
getChallenge(): Promise<PowChallengeResponse>;
|
|
22
|
+
/**
|
|
23
|
+
* Register a new account. Automatically fetches and solves a PoW challenge.
|
|
24
|
+
* The PoW solving runs in a Web Worker when available, otherwise falls back to main thread.
|
|
25
|
+
*/
|
|
26
|
+
register(input: RegisterInput): Promise<AuthResponse>;
|
|
27
|
+
login(req: LoginRequest): Promise<LoginResponse>;
|
|
28
|
+
refresh(): Promise<AuthResponse>;
|
|
29
|
+
logout(): Promise<void>;
|
|
30
|
+
totpSetup(): Promise<TotpSetupResponse>;
|
|
31
|
+
totpVerify(req: TotpVerifyRequest): Promise<void>;
|
|
32
|
+
totpDisable(): Promise<void>;
|
|
33
|
+
changePassword(req: ChangePasswordRequest): Promise<void>;
|
|
34
|
+
getSessions(): Promise<SessionResponse[]>;
|
|
35
|
+
revokeSession(familyId: string): Promise<void>;
|
|
36
|
+
getUserByUsername(username: string): Promise<import("../types.js").UserPublic>;
|
|
37
|
+
getKeyBundle(userId: string): Promise<KeyBundle>;
|
|
38
|
+
uploadPreKeys(req: UploadPreKeysRequest): Promise<void>;
|
|
39
|
+
/** Delete all unused one-time prekeys from the server (stale keys whose private keys are lost). */
|
|
40
|
+
clearPreKeys(): Promise<void>;
|
|
41
|
+
getPreKeyCount(): Promise<PreKeyCountResponse>;
|
|
42
|
+
updateKeys(req: UpdateKeysRequest): Promise<void>;
|
|
43
|
+
uploadKeyBackup(req: UploadKeyBackupRequest): Promise<void>;
|
|
44
|
+
getKeyBackup(): Promise<KeyBackupResponse>;
|
|
45
|
+
getKeyBackupStatus(): Promise<KeyBackupStatusResponse>;
|
|
46
|
+
deleteKeyBackup(): Promise<void>;
|
|
47
|
+
listServers(): Promise<ServerResponse[]>;
|
|
48
|
+
createServer(req: CreateServerRequest): Promise<ServerResponse>;
|
|
49
|
+
getServer(serverId: string): Promise<ServerResponse>;
|
|
50
|
+
getMyPermissions(serverId: string): Promise<{
|
|
51
|
+
permissions: string;
|
|
52
|
+
is_owner: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
updateServer(serverId: string, req: {
|
|
55
|
+
system_channel_id?: string | null;
|
|
56
|
+
encrypted_meta?: string;
|
|
57
|
+
}): Promise<{
|
|
58
|
+
ok: boolean;
|
|
59
|
+
}>;
|
|
60
|
+
listServerChannels(serverId: string): Promise<ChannelResponse[]>;
|
|
61
|
+
createChannel(serverId: string, req: CreateChannelRequest): Promise<ChannelResponse>;
|
|
62
|
+
joinChannel(channelId: string): Promise<void>;
|
|
63
|
+
updateChannel(channelId: string, req: {
|
|
64
|
+
encrypted_meta: string;
|
|
65
|
+
encrypted?: boolean;
|
|
66
|
+
}): Promise<ChannelResponse>;
|
|
67
|
+
deleteChannel(channelId: string): Promise<void>;
|
|
68
|
+
listDmChannels(): Promise<ChannelResponse[]>;
|
|
69
|
+
createDm(req: CreateDmRequest): Promise<ChannelResponse>;
|
|
70
|
+
createGroupDm(req: CreateGroupDmRequest): Promise<ChannelResponse>;
|
|
71
|
+
listChannelMembers(channelId: string): Promise<ChannelMemberInfo[]>;
|
|
72
|
+
leaveChannel(channelId: string): Promise<void>;
|
|
73
|
+
hideChannel(channelId: string): Promise<void>;
|
|
74
|
+
logExport(req: {
|
|
75
|
+
scope: string;
|
|
76
|
+
server_id?: string;
|
|
77
|
+
channel_id?: string;
|
|
78
|
+
message_count: number;
|
|
79
|
+
}): Promise<void>;
|
|
80
|
+
restoreServer(serverId: string, data: import("../export/types.js").HavenServerExport): Promise<RestoreServerResponse>;
|
|
81
|
+
importMessages(channelId: string, messages: ImportMessage[]): Promise<{
|
|
82
|
+
imported: number;
|
|
83
|
+
}>;
|
|
84
|
+
listCategories(serverId: string): Promise<CategoryResponse[]>;
|
|
85
|
+
createCategory(serverId: string, req: CreateCategoryRequest): Promise<CategoryResponse>;
|
|
86
|
+
updateCategory(serverId: string, categoryId: string, req: UpdateCategoryRequest): Promise<CategoryResponse>;
|
|
87
|
+
deleteCategory(serverId: string, categoryId: string): Promise<void>;
|
|
88
|
+
reorderCategories(serverId: string, req: ReorderCategoriesRequest): Promise<void>;
|
|
89
|
+
reorderChannels(serverId: string, req: ReorderChannelsRequest): Promise<void>;
|
|
90
|
+
setChannelCategory(channelId: string, req: SetChannelCategoryRequest): Promise<ChannelResponse>;
|
|
91
|
+
listRoles(serverId: string): Promise<RoleResponse[]>;
|
|
92
|
+
createRole(serverId: string, req: CreateRoleRequest): Promise<RoleResponse>;
|
|
93
|
+
updateRole(serverId: string, roleId: string, req: UpdateRoleRequest): Promise<RoleResponse>;
|
|
94
|
+
deleteRole(serverId: string, roleId: string): Promise<void>;
|
|
95
|
+
assignRole(serverId: string, userId: string, req: AssignRoleRequest): Promise<void>;
|
|
96
|
+
unassignRole(serverId: string, userId: string, roleId: string): Promise<void>;
|
|
97
|
+
listOverwrites(channelId: string): Promise<OverwriteResponse[]>;
|
|
98
|
+
setOverwrite(channelId: string, req: SetOverwriteRequest): Promise<OverwriteResponse>;
|
|
99
|
+
deleteOverwrite(channelId: string, targetType: string, targetId: string): Promise<void>;
|
|
100
|
+
getMessages(channelId: string, query?: MessageQuery): Promise<MessageResponse[]>;
|
|
101
|
+
sendMessage(channelId: string, req: SendMessageRequest): Promise<MessageResponse>;
|
|
102
|
+
getChannelReactions(channelId: string): Promise<ReactionGroup[]>;
|
|
103
|
+
getMessageReactions(messageId: string): Promise<ReactionGroup[]>;
|
|
104
|
+
getPinnedMessages(channelId: string): Promise<MessageResponse[]>;
|
|
105
|
+
getPinnedMessageIds(channelId: string): Promise<string[]>;
|
|
106
|
+
/** Upload encrypted blob directly to backend. Returns attachment_id + storage_key. */
|
|
107
|
+
uploadAttachment(blob: ArrayBuffer): Promise<UploadResponse>;
|
|
108
|
+
/** Download encrypted blob from backend. Returns raw bytes. */
|
|
109
|
+
downloadAttachment(attachmentId: string): Promise<ArrayBuffer>;
|
|
110
|
+
/** Toggle DM export consent for a channel. */
|
|
111
|
+
setExportConsent(channelId: string, allowed: boolean): Promise<void>;
|
|
112
|
+
createInvite(serverId: string, req: CreateInviteRequest): Promise<InviteResponse>;
|
|
113
|
+
listInvites(serverId: string): Promise<InviteResponse[]>;
|
|
114
|
+
deleteInvite(serverId: string, inviteId: string): Promise<void>;
|
|
115
|
+
joinByInvite(code: string): Promise<ServerResponse>;
|
|
116
|
+
listServerMembers(serverId: string): Promise<ServerMemberResponse[]>;
|
|
117
|
+
kickMember(serverId: string, userId: string): Promise<void>;
|
|
118
|
+
setNickname(serverId: string, nickname: string | null): Promise<void>;
|
|
119
|
+
setMemberNickname(serverId: string, userId: string, nickname: string | null): Promise<void>;
|
|
120
|
+
leaveServer(serverId: string): Promise<void>;
|
|
121
|
+
deleteServer(serverId: string): Promise<void>;
|
|
122
|
+
banMember(serverId: string, userId: string, req: CreateBanRequest): Promise<BanResponse>;
|
|
123
|
+
revokeBan(serverId: string, userId: string): Promise<void>;
|
|
124
|
+
listBans(serverId: string): Promise<BanResponse[]>;
|
|
125
|
+
markChannelRead(channelId: string): Promise<ReadStateResponse>;
|
|
126
|
+
getReadStates(): Promise<ChannelUnreadInfo[]>;
|
|
127
|
+
getAdminStats(): Promise<AdminStats>;
|
|
128
|
+
listAdminUsers(search?: string, limit?: number, offset?: number): Promise<AdminUserResponse[]>;
|
|
129
|
+
setUserAdmin(userId: string, isAdmin: boolean): Promise<void>;
|
|
130
|
+
adminDeleteUser(userId: string): Promise<void>;
|
|
131
|
+
timeoutMember(serverId: string, userId: string, durationSeconds: number, reason?: string): Promise<void>;
|
|
132
|
+
removeTimeout(serverId: string, userId: string): Promise<void>;
|
|
133
|
+
bulkDeleteMessages(channelId: string, messageIds: string[]): Promise<void>;
|
|
134
|
+
getAuditLog(serverId: string, opts?: {
|
|
135
|
+
limit?: number;
|
|
136
|
+
before?: string;
|
|
137
|
+
}): Promise<AuditLogEntry[]>;
|
|
138
|
+
addGroupMember(channelId: string, userId: string): Promise<void>;
|
|
139
|
+
distributeSenderKeys(channelId: string, req: DistributeSenderKeyRequest): Promise<void>;
|
|
140
|
+
getSenderKeys(channelId: string): Promise<SenderKeyDistributionResponse[]>;
|
|
141
|
+
getChannelMemberKeys(channelId: string): Promise<ChannelMemberKeyInfo[]>;
|
|
142
|
+
fetchLinkPreview(url: string): Promise<{
|
|
143
|
+
url: string;
|
|
144
|
+
title?: string;
|
|
145
|
+
description?: string;
|
|
146
|
+
image?: string;
|
|
147
|
+
site_name?: string;
|
|
148
|
+
}>;
|
|
149
|
+
getPresence(userIds: string[]): Promise<PresenceEntry[]>;
|
|
150
|
+
getUserProfile(userId: string, serverId?: string): Promise<UserProfileResponse>;
|
|
151
|
+
updateProfile(req: UpdateProfileRequest): Promise<import("../types.js").UserPublic>;
|
|
152
|
+
uploadAvatar(blob: ArrayBuffer): Promise<import("../types.js").UserPublic>;
|
|
153
|
+
uploadBanner(blob: ArrayBuffer): Promise<import("../types.js").UserPublic>;
|
|
154
|
+
distributeProfileKeys(req: import("../types.js").DistributeProfileKeysRequest): Promise<{
|
|
155
|
+
distributed: number;
|
|
156
|
+
}>;
|
|
157
|
+
getProfileKey(userId: string): Promise<import("../types.js").ProfileKeyResponse>;
|
|
158
|
+
listFriends(): Promise<FriendResponse[]>;
|
|
159
|
+
sendFriendRequest(req: FriendRequestBody): Promise<FriendResponse>;
|
|
160
|
+
acceptFriendRequest(friendshipId: string): Promise<FriendResponse>;
|
|
161
|
+
declineFriendRequest(friendshipId: string): Promise<void>;
|
|
162
|
+
removeFriend(friendshipId: string): Promise<void>;
|
|
163
|
+
listDmRequests(): Promise<ChannelResponse[]>;
|
|
164
|
+
handleDmRequest(channelId: string, req: DmRequestAction): Promise<void>;
|
|
165
|
+
updateDmPrivacy(req: UpdateDmPrivacyRequest): Promise<void>;
|
|
166
|
+
blockUser(userId: string): Promise<void>;
|
|
167
|
+
unblockUser(userId: string): Promise<void>;
|
|
168
|
+
getBlockedUsers(): Promise<BlockedUserResponse[]>;
|
|
169
|
+
reportMessage(req: CreateReportRequest): Promise<ReportResponse>;
|
|
170
|
+
listServerEmojis(serverId: string): Promise<CustomEmojiResponse[]>;
|
|
171
|
+
uploadEmoji(serverId: string, name: string, imageData: ArrayBuffer): Promise<CustomEmojiResponse>;
|
|
172
|
+
renameEmoji(serverId: string, emojiId: string, name: string): Promise<CustomEmojiResponse>;
|
|
173
|
+
deleteEmoji(serverId: string, emojiId: string): Promise<void>;
|
|
174
|
+
uploadServerIcon(serverId: string, blob: ArrayBuffer): Promise<{
|
|
175
|
+
icon_url: string;
|
|
176
|
+
}>;
|
|
177
|
+
deleteServerIcon(serverId: string): Promise<void>;
|
|
178
|
+
/** Check if the instance requires an invite code to register (no auth needed). */
|
|
179
|
+
checkInviteRequired(): Promise<InviteRequiredResponse>;
|
|
180
|
+
/** List the current user's registration invite codes. */
|
|
181
|
+
listMyRegistrationInvites(): Promise<RegistrationInviteResponse[]>;
|
|
182
|
+
deleteAccount(password: string): Promise<void>;
|
|
183
|
+
joinVoice(channelId: string): Promise<VoiceTokenResponse>;
|
|
184
|
+
leaveVoice(channelId: string): Promise<void>;
|
|
185
|
+
getVoiceParticipants(channelId: string): Promise<VoiceParticipant[]>;
|
|
186
|
+
serverMuteUser(channelId: string, userId: string, muted: boolean): Promise<void>;
|
|
187
|
+
serverDeafenUser(channelId: string, userId: string, deafened: boolean): Promise<void>;
|
|
188
|
+
searchGifs(query: string, offset?: number): Promise<GifSearchResponse>;
|
|
189
|
+
trendingGifs(): Promise<GifSearchResponse>;
|
|
190
|
+
private get;
|
|
191
|
+
private post;
|
|
192
|
+
private put;
|
|
193
|
+
private patch;
|
|
194
|
+
private delete;
|
|
195
|
+
private request;
|
|
196
|
+
}
|
|
197
|
+
export declare class HavenApiError extends Error {
|
|
198
|
+
status: number;
|
|
199
|
+
constructor(message: string, status: number);
|
|
200
|
+
}
|