@majikah/majik-signature-client 0.6.2 → 0.6.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 +110 -0
- package/dist/core/backup/constants.d.ts +3 -3
- package/dist/core/client-state-manager.d.ts +3 -77
- package/dist/core/client-state-manager.js +3 -147
- package/dist/core/contacts/majik-contact-directory.d.ts +3 -2
- package/dist/core/contacts/majik-contact-directory.js +8 -8
- package/dist/core/contacts/majik-contact-manager.d.ts +3 -2
- package/dist/core/contacts/majik-contact-manager.js +5 -5
- package/dist/core/storage/client-state/adapter-sql.js +8 -8
- package/dist/core/storage/contact-directory/contacts/adapter-sql.js +4 -4
- package/dist/core/storage/contact-directory/groups/adapter-sql.js +4 -4
- package/dist/core/storage/keystore/adapter-sql.js +4 -4
- package/dist/core/storage/sql-schema.js +67 -67
- package/dist/core/storage/stamps/adapter-sql.js +4 -4
- package/dist/core/types.d.ts +0 -4
- package/dist/majik-signature-client.d.ts +28 -427
- package/dist/majik-signature-client.js +58 -739
- package/package.json +5 -4
- package/dist/core/contacts/majik-contact.d.ts +0 -89
- package/dist/core/contacts/majik-contact.js +0 -214
- package/dist/core/crypto/keystore.d.ts +0 -228
- package/dist/core/crypto/keystore.js +0 -575
- package/dist/core/utils/APITranscoder.d.ts +0 -114
- package/dist/core/utils/APITranscoder.js +0 -305
- package/dist/core/utils/idb-majik-system.d.ts +0 -14
- package/dist/core/utils/idb-majik-system.js +0 -44
- package/dist/core/utils/majik-file-utils.d.ts +0 -16
- package/dist/core/utils/majik-file-utils.js +0 -153
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Majik Signature Client
|
|
2
|
+
|
|
3
|
+
**Majik Signature Client** is a high-level TypeScript wrapper for `MajikSignature`, built directly on top of `MajikKeyClient`.
|
|
4
|
+
|
|
5
|
+
Designed to be used natively within the Majikah ecosystem, it inherits all core `MajikKey` account management—such as creation, lock/unlock states, and active-account tracking—while introducing domain-specific functionality for file signing, contact management, and encrypted reusable stamps.
|
|
6
|
+
|
|
7
|
+
## Core Capabilities
|
|
8
|
+
|
|
9
|
+
* **Integrated Key & Account Management**: Extends `MajikKeyClient` to seamlessly handle account lifecycles and cryptographic unlocking. Accounts can easily be shared alongside `MajikMessage`.
|
|
10
|
+
* **Contact & Group Directory**: Utilizes `MajikContactManager` for full contact CRUD operations, including system/user groups, favorites, and blocked lists.
|
|
11
|
+
* **Post-Quantum Signing & Verification**: Provides intuitive methods (`signFile`, `verifyFile`, `batchSignFiles`) for text, raw bytes, and file blobs. Verifications automatically resolve public keys from the internal contact directory or active keystore.
|
|
12
|
+
* **Multi-sig & Envelope Sealing**: Built-in support for restricted allowlists (`expectedSigners`), multi-sig pending states, and cryptographic sealing to prevent further modifications to a file.
|
|
13
|
+
* **Encrypted Stamp Assets**: Uses `MajikSignatureStampManager` to securely create, encrypt, and manage reusable assets (signatures, audio, text) bound to the user's ML-KEM keys.
|
|
14
|
+
* **Data Backup & Restore**: Robust export and hydration capabilities for contacts, stamps, and user preferences utilizing `MajikCompressedJSON` and protected by strict magic byte headers.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Initialization
|
|
19
|
+
|
|
20
|
+
You can instantiate and fully hydrate the client and its storage adapters in a single call using the static `create` method.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { MajikSignatureClient } from "your-path-here"; // Update with actual package export
|
|
24
|
+
|
|
25
|
+
// 1. Initialize and hydrate the client state, keys, contacts, and stamps
|
|
26
|
+
const client = await MajikSignatureClient.create({
|
|
27
|
+
adapters: {
|
|
28
|
+
// Inject your target storage adapters here (e.g., IndexedDB)
|
|
29
|
+
contacts: myContactsAdapter,
|
|
30
|
+
stamps: myStampsAdapter
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick Usage API
|
|
38
|
+
|
|
39
|
+
### 1. File Signing & Sealing
|
|
40
|
+
|
|
41
|
+
Sign a file using the currently active unlocked account, then restrict future signers and seal it.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Sign a file blob
|
|
45
|
+
const { blob: signedBlob, signature } = await client.signFile(myFile, {
|
|
46
|
+
contentType: "application/pdf"
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Optionally seal a multi-sig file to lock the envelope
|
|
50
|
+
const { sealInfo } = await client.seal(signedBlob);
|
|
51
|
+
console.log("Sealed at:", sealInfo.sealTimestamp);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. File Verification
|
|
55
|
+
|
|
56
|
+
Verify a file's embedded signature automatically using your contact directory to resolve the signer's identity.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Verifies against known contacts or extracts self-reported keys
|
|
60
|
+
const result = await client.verifyFile(signedBlob, {
|
|
61
|
+
contactId: "expected_contact_id"
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (result.valid) {
|
|
65
|
+
console.log(`Authentic. Signed by: ${result.signerLabel || result.signerId}`);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Contact Management
|
|
70
|
+
|
|
71
|
+
Add external contacts and organize them into groups for streamlined verification.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Import a contact from a compressed base64 string
|
|
75
|
+
const contact = await client.importContactCompressed(base64ContactString);
|
|
76
|
+
|
|
77
|
+
// Add the contact to a custom group
|
|
78
|
+
const myGroup = await client.createGroup("group_1", "Trusted Signers");
|
|
79
|
+
await client.addContactToGroup(myGroup.id, contact.id);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 4. Encrypted Stamps
|
|
83
|
+
|
|
84
|
+
Create an encrypted asset (like an image signature) that is safely stored at rest and decrypted on the fly using ML-KEM.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Create a new image stamp
|
|
88
|
+
const stamp = await client.createStamp(
|
|
89
|
+
imageBytes,
|
|
90
|
+
"image",
|
|
91
|
+
"My Primary Signature",
|
|
92
|
+
{ mimeType: "image/png" }
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Decrypt content later for injection into a document
|
|
96
|
+
const decryptedBytes = await client.decryptStampContent(stamp.id);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 5. App Data Backups
|
|
100
|
+
|
|
101
|
+
Export or restore the entire application state—including contacts, stamps, and preferences—into a `.MJKB` portable blob.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// Export
|
|
105
|
+
const backupBlob = await client.backupAppData();
|
|
106
|
+
|
|
107
|
+
// Restore
|
|
108
|
+
const restoreResults = await client.restoreAppData(backupBlob);
|
|
109
|
+
console.log(`Restored ${restoreResults.contacts} contacts.`);
|
|
110
|
+
```
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* MJKA = MajiK App-data
|
|
8
8
|
*/
|
|
9
9
|
export declare const MAJIK_SIGNATURE_BACKUP_MAGIC: {
|
|
10
|
-
readonly stamps: Uint8Array
|
|
11
|
-
readonly contacts: Uint8Array
|
|
12
|
-
readonly appData: Uint8Array
|
|
10
|
+
readonly stamps: Uint8Array<ArrayBuffer>;
|
|
11
|
+
readonly contacts: Uint8Array<ArrayBuffer>;
|
|
12
|
+
readonly appData: Uint8Array<ArrayBuffer>;
|
|
13
13
|
};
|
|
14
14
|
/** The union of valid magic-byte headers. */
|
|
15
15
|
export type MajikMessageBackupMagic = (typeof MAJIK_SIGNATURE_BACKUP_MAGIC)[keyof typeof MAJIK_SIGNATURE_BACKUP_MAGIC];
|
|
@@ -24,84 +24,10 @@
|
|
|
24
24
|
* hydrate() pass. Callers should never need to hydrate() again unless the
|
|
25
25
|
* adapter is swapped.
|
|
26
26
|
*/
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
private _cache;
|
|
31
|
-
private _adapter;
|
|
32
|
-
/**
|
|
33
|
-
* @param adapter Defaults to InMemoryClientStateAdapter (non-persistent).
|
|
34
|
-
* Pass IDBClientStateAdapter or SQLiteClientStateAdapter for persistence.
|
|
35
|
-
*/
|
|
27
|
+
import { MajikKeyClientStateManager } from "@majikah/majik-key-client";
|
|
28
|
+
import { ClientStateStorageAdapter, UserAppPreferences } from "./storage/client-state/_types";
|
|
29
|
+
export declare class ClientStateManager extends MajikKeyClientStateManager {
|
|
36
30
|
constructor(adapter?: ClientStateStorageAdapter);
|
|
37
|
-
get adapter(): ClientStateStorageAdapter;
|
|
38
|
-
/**
|
|
39
|
-
* Swap the storage adapter at runtime.
|
|
40
|
-
*
|
|
41
|
-
* Does NOT migrate data. To migrate:
|
|
42
|
-
* ```ts
|
|
43
|
-
* const entries = stateManager.listCachedEntries();
|
|
44
|
-
* stateManager.setAdapter(newAdapter);
|
|
45
|
-
* await stateManager.hydrate();
|
|
46
|
-
* await stateManager.bulkSet(entries);
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
setAdapter(adapter: ClientStateStorageAdapter): void;
|
|
50
|
-
/**
|
|
51
|
-
* Load all entries from the adapter into the in-memory cache.
|
|
52
|
-
* Call once after construction (MajikSignatureClient.hydrate() does this).
|
|
53
|
-
*/
|
|
54
|
-
hydrate(): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* Retrieve a raw JSON string for any key. Returns `null` if not found.
|
|
57
|
-
* Prefer the typed accessors (getAccountOrder, getInvoiceDefaults) over this.
|
|
58
|
-
*/
|
|
59
|
-
get(id: string): Promise<string | null>;
|
|
60
|
-
/**
|
|
61
|
-
* Persist a raw JSON string for any key.
|
|
62
|
-
* Prefer the typed accessors over this.
|
|
63
|
-
*/
|
|
64
|
-
set(id: string, value: string): Promise<void>;
|
|
65
|
-
/**
|
|
66
|
-
* Remove a single key.
|
|
67
|
-
*/
|
|
68
|
-
remove(id: string): Promise<boolean>;
|
|
69
|
-
/**
|
|
70
|
-
* Remove all stored state.
|
|
71
|
-
*/
|
|
72
|
-
clear(): Promise<void>;
|
|
73
|
-
/**
|
|
74
|
-
* Whether a key exists in the cache.
|
|
75
|
-
* Accurate after hydrate(); use exists() for an authoritative adapter check.
|
|
76
|
-
*/
|
|
77
|
-
hasCached(id: string): boolean;
|
|
78
|
-
/**
|
|
79
|
-
* Authoritative existence check against the adapter.
|
|
80
|
-
*/
|
|
81
|
-
exists(id: string): Promise<boolean>;
|
|
82
|
-
/**
|
|
83
|
-
* Snapshot of all cached entries — useful for adapter migration.
|
|
84
|
-
*/
|
|
85
|
-
listCachedEntries(): ClientStateEntry[];
|
|
86
|
-
/**
|
|
87
|
-
* Persist multiple entries in one adapter call.
|
|
88
|
-
*/
|
|
89
|
-
bulkSet(entries: ClientStateEntry[]): Promise<void>;
|
|
90
|
-
/**
|
|
91
|
-
* Retrieve the ordered list of own account IDs.
|
|
92
|
-
* Returns `null` if no order has been persisted yet.
|
|
93
|
-
*/
|
|
94
|
-
getAccountOrder(): Promise<AccountOrderValue | null>;
|
|
95
|
-
/**
|
|
96
|
-
* Persist the ordered list of own account IDs.
|
|
97
|
-
*/
|
|
98
|
-
setAccountOrder(order: AccountOrderValue): Promise<void>;
|
|
99
|
-
/**
|
|
100
|
-
* Remove the persisted account order (resets to insertion order on next
|
|
101
|
-
* hydrate).
|
|
102
|
-
*/
|
|
103
|
-
removeAccountOrder(): Promise<void>;
|
|
104
|
-
count(): Promise<number>;
|
|
105
31
|
/**
|
|
106
32
|
* Retrieve user app preferences.
|
|
107
33
|
* Returns `null` if none have been saved yet.
|
|
@@ -24,159 +24,15 @@
|
|
|
24
24
|
* hydrate() pass. Callers should never need to hydrate() again unless the
|
|
25
25
|
* adapter is swapped.
|
|
26
26
|
*/
|
|
27
|
+
import { MajikKeyClientStateManager } from "@majikah/majik-key-client";
|
|
27
28
|
import { CLIENT_STATE_KEYS, } from "./storage/client-state/_types";
|
|
28
29
|
import { InMemoryClientStateAdapter } from "./storage/client-state/adapter-memory";
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
// ClientStateManager
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
|
-
export class ClientStateManager {
|
|
33
|
-
/** In-memory cache — warmed by hydrate(), kept in sync on every write. */
|
|
34
|
-
_cache = new Map();
|
|
35
|
-
_adapter;
|
|
36
|
-
/**
|
|
37
|
-
* @param adapter Defaults to InMemoryClientStateAdapter (non-persistent).
|
|
38
|
-
* Pass IDBClientStateAdapter or SQLiteClientStateAdapter for persistence.
|
|
39
|
-
*/
|
|
33
|
+
export class ClientStateManager extends MajikKeyClientStateManager {
|
|
40
34
|
constructor(adapter = new InMemoryClientStateAdapter()) {
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
// ── Adapter management ────────────────────────────────────────────────────
|
|
44
|
-
get adapter() {
|
|
45
|
-
return this._adapter;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Swap the storage adapter at runtime.
|
|
49
|
-
*
|
|
50
|
-
* Does NOT migrate data. To migrate:
|
|
51
|
-
* ```ts
|
|
52
|
-
* const entries = stateManager.listCachedEntries();
|
|
53
|
-
* stateManager.setAdapter(newAdapter);
|
|
54
|
-
* await stateManager.hydrate();
|
|
55
|
-
* await stateManager.bulkSet(entries);
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
setAdapter(adapter) {
|
|
59
|
-
this._adapter = adapter;
|
|
60
|
-
}
|
|
61
|
-
// ── Hydration ─────────────────────────────────────────────────────────────
|
|
62
|
-
/**
|
|
63
|
-
* Load all entries from the adapter into the in-memory cache.
|
|
64
|
-
* Call once after construction (MajikSignatureClient.hydrate() does this).
|
|
65
|
-
*/
|
|
66
|
-
async hydrate() {
|
|
67
|
-
const entries = await this._adapter.list();
|
|
68
|
-
this._cache.clear();
|
|
69
|
-
for (const entry of entries) {
|
|
70
|
-
this._cache.set(entry.id, entry.value);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// ── Generic typed get / set / remove ─────────────────────────────────────
|
|
74
|
-
/**
|
|
75
|
-
* Retrieve a raw JSON string for any key. Returns `null` if not found.
|
|
76
|
-
* Prefer the typed accessors (getAccountOrder, getInvoiceDefaults) over this.
|
|
77
|
-
*/
|
|
78
|
-
async get(id) {
|
|
79
|
-
const cached = this._cache.get(id);
|
|
80
|
-
if (cached !== undefined)
|
|
81
|
-
return cached;
|
|
82
|
-
// Cache miss — should not happen after hydrate() but defensive
|
|
83
|
-
const entry = await this._adapter.getById(id);
|
|
84
|
-
if (!entry)
|
|
85
|
-
return null;
|
|
86
|
-
this._cache.set(id, entry.value);
|
|
87
|
-
return entry.value;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Persist a raw JSON string for any key.
|
|
91
|
-
* Prefer the typed accessors over this.
|
|
92
|
-
*/
|
|
93
|
-
async set(id, value) {
|
|
94
|
-
const entry = { id, value };
|
|
95
|
-
await this._adapter.save(entry);
|
|
96
|
-
this._cache.set(id, value);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Remove a single key.
|
|
100
|
-
*/
|
|
101
|
-
async remove(id) {
|
|
102
|
-
const removed = await this._adapter.remove(id);
|
|
103
|
-
this._cache.delete(id);
|
|
104
|
-
return removed;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Remove all stored state.
|
|
108
|
-
*/
|
|
109
|
-
async clear() {
|
|
110
|
-
await this._adapter.clear();
|
|
111
|
-
this._cache.clear();
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Whether a key exists in the cache.
|
|
115
|
-
* Accurate after hydrate(); use exists() for an authoritative adapter check.
|
|
116
|
-
*/
|
|
117
|
-
hasCached(id) {
|
|
118
|
-
return this._cache.has(id);
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Authoritative existence check against the adapter.
|
|
122
|
-
*/
|
|
123
|
-
async exists(id) {
|
|
124
|
-
if (this._cache.has(id))
|
|
125
|
-
return true;
|
|
126
|
-
return this._adapter.exists(id);
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Snapshot of all cached entries — useful for adapter migration.
|
|
130
|
-
*/
|
|
131
|
-
listCachedEntries() {
|
|
132
|
-
return Array.from(this._cache.entries()).map(([id, value]) => ({
|
|
133
|
-
id,
|
|
134
|
-
value,
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Persist multiple entries in one adapter call.
|
|
139
|
-
*/
|
|
140
|
-
async bulkSet(entries) {
|
|
141
|
-
if (entries.length === 0)
|
|
142
|
-
return;
|
|
143
|
-
await this._adapter.bulkSave(entries);
|
|
144
|
-
for (const e of entries)
|
|
145
|
-
this._cache.set(e.id, e.value);
|
|
146
|
-
}
|
|
147
|
-
// ── Typed: account order ──────────────────────────────────────────────────
|
|
148
|
-
/**
|
|
149
|
-
* Retrieve the ordered list of own account IDs.
|
|
150
|
-
* Returns `null` if no order has been persisted yet.
|
|
151
|
-
*/
|
|
152
|
-
async getAccountOrder() {
|
|
153
|
-
const raw = await this.get(CLIENT_STATE_KEYS.ACCOUNT_ORDER);
|
|
154
|
-
if (raw === null)
|
|
155
|
-
return null;
|
|
156
|
-
try {
|
|
157
|
-
return JSON.parse(raw);
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
console.warn("ClientStateManager: malformed account order — discarding.");
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Persist the ordered list of own account IDs.
|
|
166
|
-
*/
|
|
167
|
-
async setAccountOrder(order) {
|
|
168
|
-
await this.set(CLIENT_STATE_KEYS.ACCOUNT_ORDER, JSON.stringify(order));
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Remove the persisted account order (resets to insertion order on next
|
|
172
|
-
* hydrate).
|
|
173
|
-
*/
|
|
174
|
-
async removeAccountOrder() {
|
|
175
|
-
await this.remove(CLIENT_STATE_KEYS.ACCOUNT_ORDER);
|
|
176
|
-
}
|
|
177
|
-
// ── Async count ───────────────────────────────────────────────────────────
|
|
178
|
-
async count() {
|
|
179
|
-
return this._adapter.count();
|
|
35
|
+
super(adapter);
|
|
180
36
|
}
|
|
181
37
|
/**
|
|
182
38
|
* Retrieve user app preferences.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MAJIK_API_RESPONSE } from "../types";
|
|
2
2
|
import { MajikContact, MajikContactData } from "@majikah/majik-contact";
|
|
3
3
|
import { MajikContactDirectoryData } from "./types";
|
|
4
|
+
import { MajikKeyAddress } from "@majikah/majik-key";
|
|
4
5
|
export declare class MajikContactDirectory {
|
|
5
6
|
private contacts;
|
|
6
7
|
private fingerprintMap;
|
|
@@ -15,7 +16,7 @@ export declare class MajikContactDirectory {
|
|
|
15
16
|
* Get contact by public key (base64)
|
|
16
17
|
* Uses MajikContact.getPublicKeyBase64() for canonical comparison
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
getContactByAddress(address: MajikKeyAddress): Promise<MajikContact | undefined>;
|
|
19
20
|
hasFingerprint(fingerprint: string): boolean;
|
|
20
21
|
listContacts(sortedByLabel?: boolean, majikahOnly?: boolean): MajikContact[];
|
|
21
22
|
blockContact(id: string): MajikContact;
|
|
@@ -24,7 +25,7 @@ export declare class MajikContactDirectory {
|
|
|
24
25
|
/**
|
|
25
26
|
* Checks if a contact exists by their public key (base64)
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
|
+
hasContactByAddress(address: MajikKeyAddress): Promise<boolean>;
|
|
28
29
|
clear(): this;
|
|
29
30
|
setMajikahStatus(id: string, status: boolean): MajikContact;
|
|
30
31
|
isMajikahIdentityChecked(id: string): boolean;
|
|
@@ -78,13 +78,13 @@ export class MajikContactDirectory {
|
|
|
78
78
|
* Get contact by public key (base64)
|
|
79
79
|
* Uses MajikContact.getPublicKeyBase64() for canonical comparison
|
|
80
80
|
*/
|
|
81
|
-
async
|
|
82
|
-
if (!
|
|
83
|
-
throw new MajikContactDirectoryError("Public key must be a non-empty base64
|
|
81
|
+
async getContactByAddress(address) {
|
|
82
|
+
if (!address || typeof address !== "string") {
|
|
83
|
+
throw new MajikContactDirectoryError("Public key must be a non-empty base64 MajikKeyAddress");
|
|
84
84
|
}
|
|
85
85
|
for (const contact of this.contacts.values()) {
|
|
86
86
|
const contactKey = await contact.getPublicKeyBase64();
|
|
87
|
-
if (contactKey ===
|
|
87
|
+
if (contactKey === address) {
|
|
88
88
|
return contact;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -121,11 +121,11 @@ export class MajikContactDirectory {
|
|
|
121
121
|
/**
|
|
122
122
|
* Checks if a contact exists by their public key (base64)
|
|
123
123
|
*/
|
|
124
|
-
async
|
|
125
|
-
if (!
|
|
126
|
-
throw new MajikContactDirectoryError("Public key must be a non-empty base64
|
|
124
|
+
async hasContactByAddress(address) {
|
|
125
|
+
if (!address || typeof address !== "string") {
|
|
126
|
+
throw new MajikContactDirectoryError("Public key must be a non-empty base64 MajikKeyAddress");
|
|
127
127
|
}
|
|
128
|
-
const contact = await this.
|
|
128
|
+
const contact = await this.getContactByAddress(address);
|
|
129
129
|
return contact !== undefined;
|
|
130
130
|
}
|
|
131
131
|
clear() {
|
|
@@ -5,6 +5,7 @@ import { MAJIK_API_RESPONSE } from "../types";
|
|
|
5
5
|
import { MajikContactManagerJSON } from "./types";
|
|
6
6
|
import { MajikContactStorageAdapter } from "../storage/contact-directory/contacts/_types";
|
|
7
7
|
import { MajikContactGroupStorageAdapter } from "../storage/contact-directory/groups/_types";
|
|
8
|
+
import { MajikKeyAddress } from "@majikah/majik-key";
|
|
8
9
|
export interface MajikContactManagerAdapters {
|
|
9
10
|
contacts?: MajikContactStorageAdapter;
|
|
10
11
|
groups?: MajikContactGroupStorageAdapter;
|
|
@@ -82,12 +83,12 @@ export declare class MajikContactManager {
|
|
|
82
83
|
clear(): Promise<this>;
|
|
83
84
|
getContact(id: string): MajikContact | undefined;
|
|
84
85
|
getContactByFingerprint(fingerprint: string): MajikContact | undefined;
|
|
85
|
-
|
|
86
|
+
getContactByAddress(address: MajikKeyAddress): Promise<MajikContact | undefined>;
|
|
86
87
|
getContactsByIds(ids: string[], strict?: boolean): MajikContact[];
|
|
87
88
|
getContactsByPublicKeys(publicKeys: string[], strict?: boolean): Promise<MajikContact[]>;
|
|
88
89
|
hasContact(id: string): boolean;
|
|
89
90
|
hasFingerprint(fingerprint: string): boolean;
|
|
90
|
-
|
|
91
|
+
hasContactByAddress(address: MajikKeyAddress): Promise<boolean>;
|
|
91
92
|
listContacts(sortedByLabel?: boolean, majikahOnly?: boolean): MajikContact[];
|
|
92
93
|
isMajikahRegistered(id: string): boolean;
|
|
93
94
|
isMajikahIdentityChecked(id: string): boolean;
|
|
@@ -190,8 +190,8 @@ export class MajikContactManager {
|
|
|
190
190
|
getContactByFingerprint(fingerprint) {
|
|
191
191
|
return this.directory.getContactByFingerprint(fingerprint);
|
|
192
192
|
}
|
|
193
|
-
async
|
|
194
|
-
return await this.directory.
|
|
193
|
+
async getContactByAddress(address) {
|
|
194
|
+
return await this.directory.getContactByAddress(address);
|
|
195
195
|
}
|
|
196
196
|
getContactsByIds(ids, strict = false) {
|
|
197
197
|
if (!ids?.length)
|
|
@@ -218,7 +218,7 @@ export class MajikContactManager {
|
|
|
218
218
|
return [];
|
|
219
219
|
const uniqueKeys = [...new Set(publicKeys)];
|
|
220
220
|
const contacts = await Promise.all(uniqueKeys.map(async (key) => {
|
|
221
|
-
const contact = await this.directory.
|
|
221
|
+
const contact = await this.directory.getContactByAddress(key);
|
|
222
222
|
if (!contact && strict) {
|
|
223
223
|
throw new MajikContactManagerError(`Contact not found for publicKey: ${key}`);
|
|
224
224
|
}
|
|
@@ -232,8 +232,8 @@ export class MajikContactManager {
|
|
|
232
232
|
hasFingerprint(fingerprint) {
|
|
233
233
|
return this.directory.hasFingerprint(fingerprint);
|
|
234
234
|
}
|
|
235
|
-
async
|
|
236
|
-
return this.directory.
|
|
235
|
+
async hasContactByAddress(address) {
|
|
236
|
+
return this.directory.hasContactByAddress(address);
|
|
237
237
|
}
|
|
238
238
|
listContacts(sortedByLabel = false, majikahOnly = false) {
|
|
239
239
|
return this.directory.listContacts(sortedByLabel, majikahOnly);
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
*/
|
|
20
20
|
const TABLE = "majik_client_state";
|
|
21
21
|
/** DDL — pass to your migration runner or call createSchema() directly. */
|
|
22
|
-
export const MAJIK_CLIENT_STATE_SCHEMA = `
|
|
23
|
-
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
24
|
-
key TEXT PRIMARY KEY,
|
|
25
|
-
value TEXT NOT NULL,
|
|
26
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
27
|
-
);
|
|
22
|
+
export const MAJIK_CLIENT_STATE_SCHEMA = `
|
|
23
|
+
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
24
|
+
key TEXT PRIMARY KEY,
|
|
25
|
+
value TEXT NOT NULL,
|
|
26
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
27
|
+
);
|
|
28
28
|
`.trim();
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
// Adapter
|
|
@@ -44,7 +44,7 @@ export class SQLiteClientStateAdapter {
|
|
|
44
44
|
// }
|
|
45
45
|
// ── Write ──────────────────────────────────────────────────────────────────
|
|
46
46
|
async save(entry) {
|
|
47
|
-
await this.db.run(`INSERT OR REPLACE INTO ${TABLE} (key, value, updated_at)
|
|
47
|
+
await this.db.run(`INSERT OR REPLACE INTO ${TABLE} (key, value, updated_at)
|
|
48
48
|
VALUES (?, ?, datetime('now'))`, [entry.id, entry.value]);
|
|
49
49
|
}
|
|
50
50
|
async bulkSave(entries) {
|
|
@@ -52,7 +52,7 @@ export class SQLiteClientStateAdapter {
|
|
|
52
52
|
return;
|
|
53
53
|
await this.db.transaction(async (tx) => {
|
|
54
54
|
for (const entry of entries) {
|
|
55
|
-
await tx.run(`INSERT OR REPLACE INTO ${TABLE} (key, value, updated_at)
|
|
55
|
+
await tx.run(`INSERT OR REPLACE INTO ${TABLE} (key, value, updated_at)
|
|
56
56
|
VALUES (?, ?, datetime('now'))`, [entry.id, entry.value]);
|
|
57
57
|
}
|
|
58
58
|
});
|
|
@@ -5,8 +5,8 @@ export class SQLiteContactAdapter {
|
|
|
5
5
|
this.db = db;
|
|
6
6
|
}
|
|
7
7
|
async save(contact) {
|
|
8
|
-
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}
|
|
9
|
-
(id, json, fingerprint, label, created_at, updated_at)
|
|
8
|
+
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}
|
|
9
|
+
(id, json, fingerprint, label, created_at, updated_at)
|
|
10
10
|
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
11
11
|
contact.id,
|
|
12
12
|
JSON.stringify(contact),
|
|
@@ -47,8 +47,8 @@ export class SQLiteContactAdapter {
|
|
|
47
47
|
return;
|
|
48
48
|
await this.db.transaction(async (tx) => {
|
|
49
49
|
for (const c of contacts) {
|
|
50
|
-
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}
|
|
51
|
-
(id, json, fingerprint, label, created_at, updated_at)
|
|
50
|
+
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}
|
|
51
|
+
(id, json, fingerprint, label, created_at, updated_at)
|
|
52
52
|
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
53
53
|
c.id,
|
|
54
54
|
JSON.stringify(c),
|
|
@@ -5,8 +5,8 @@ export class SQLiteContactGroupAdapter {
|
|
|
5
5
|
this.db = db;
|
|
6
6
|
}
|
|
7
7
|
async save(group) {
|
|
8
|
-
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}
|
|
9
|
-
(id, json, name, created_at, updated_at, is_system)
|
|
8
|
+
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}
|
|
9
|
+
(id, json, name, created_at, updated_at, is_system)
|
|
10
10
|
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
11
11
|
group.id,
|
|
12
12
|
JSON.stringify(group),
|
|
@@ -47,8 +47,8 @@ export class SQLiteContactGroupAdapter {
|
|
|
47
47
|
return;
|
|
48
48
|
await this.db.transaction(async (tx) => {
|
|
49
49
|
for (const g of groups) {
|
|
50
|
-
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}
|
|
51
|
-
(id, json, name, created_at, updated_at, is_system)
|
|
50
|
+
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}
|
|
51
|
+
(id, json, name, created_at, updated_at, is_system)
|
|
52
52
|
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
53
53
|
g.id,
|
|
54
54
|
JSON.stringify(g),
|
|
@@ -5,8 +5,8 @@ export class SQLiteKeystoreAdapter {
|
|
|
5
5
|
this.db = db;
|
|
6
6
|
}
|
|
7
7
|
async save(key) {
|
|
8
|
-
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}
|
|
9
|
-
(id, json, timestamp, public_key)
|
|
8
|
+
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}
|
|
9
|
+
(id, json, timestamp, public_key)
|
|
10
10
|
VALUES (?, ?, ?, ?)`, [
|
|
11
11
|
key.id,
|
|
12
12
|
JSON.stringify(key),
|
|
@@ -45,8 +45,8 @@ export class SQLiteKeystoreAdapter {
|
|
|
45
45
|
return;
|
|
46
46
|
await this.db.transaction(async (tx) => {
|
|
47
47
|
for (const g of keys) {
|
|
48
|
-
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}
|
|
49
|
-
(id, json, timestamp, public_key)
|
|
48
|
+
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}
|
|
49
|
+
(id, json, timestamp, public_key)
|
|
50
50
|
VALUES (?, ?, ?, ?)`, [
|
|
51
51
|
g.id,
|
|
52
52
|
JSON.stringify(g),
|