@majikah/majik-universal-id-client 0.0.16 → 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/dist/core/client-state-manager.d.ts +13 -69
- package/dist/core/client-state-manager.js +40 -202
- package/dist/core/contacts/majik-contact-directory.d.ts +3 -2
- package/dist/core/contacts/majik-contact-directory.js +8 -11
- package/dist/core/contacts/majik-contact-manager.d.ts +4 -7
- package/dist/core/contacts/majik-contact-manager.js +6 -73
- package/dist/core/storage/client-state/_types.d.ts +39 -3
- package/dist/core/storage/client-state/_types.js +3 -2
- 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 +43 -43
- package/dist/core/types.d.ts +0 -44
- package/dist/core/types.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/majik-universal-id-client.d.ts +79 -158
- package/dist/majik-universal-id-client.js +296 -457
- package/package.json +10 -10
- package/dist/core/contacts/majik-contact.d.ts +0 -89
- package/dist/core/contacts/majik-contact.js +0 -212
- 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 -15
- 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
|
@@ -24,82 +24,26 @@
|
|
|
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 (MajikBuwizClient.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
31
|
/**
|
|
87
|
-
*
|
|
32
|
+
* Retrieve user app preferences.
|
|
33
|
+
* Returns `null` if none have been saved yet.
|
|
88
34
|
*/
|
|
89
|
-
|
|
35
|
+
getUserAppPreferences(): Promise<UserAppPreferences>;
|
|
90
36
|
/**
|
|
91
|
-
*
|
|
92
|
-
* Returns `null` if no order has been persisted yet.
|
|
37
|
+
* Persist user app preferences.
|
|
93
38
|
*/
|
|
94
|
-
|
|
39
|
+
setUserAppPreferences(preferences: UserAppPreferences): Promise<void>;
|
|
95
40
|
/**
|
|
96
|
-
* Persist
|
|
41
|
+
* Persist user app preferences.
|
|
97
42
|
*/
|
|
98
|
-
|
|
43
|
+
resetUserAppPreferences(): Promise<void>;
|
|
99
44
|
/**
|
|
100
|
-
* Remove
|
|
101
|
-
* hydrate).
|
|
45
|
+
* Remove user app preferences.
|
|
102
46
|
*/
|
|
103
|
-
|
|
104
|
-
count(): Promise<number>;
|
|
47
|
+
removeUserAppPreferences(): Promise<void>;
|
|
105
48
|
}
|
|
49
|
+
export declare const DEFAULT_USER_APP_PREFERENCES: UserAppPreferences;
|
|
@@ -24,227 +24,65 @@
|
|
|
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;
|
|
35
|
+
super(adapter);
|
|
46
36
|
}
|
|
47
37
|
/**
|
|
48
|
-
*
|
|
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
|
-
* ```
|
|
38
|
+
* Retrieve user app preferences.
|
|
39
|
+
* Returns `null` if none have been saved yet.
|
|
57
40
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* Load all entries from the adapter into the in-memory cache.
|
|
64
|
-
* Call once after construction (MajikBuwizClient.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);
|
|
41
|
+
async getUserAppPreferences() {
|
|
42
|
+
const raw = await this.get(CLIENT_STATE_KEYS.USER_APP_PREFERENCES);
|
|
43
|
+
if (raw === null) {
|
|
44
|
+
await this.resetUserAppPreferences();
|
|
45
|
+
return DEFAULT_USER_APP_PREFERENCES;
|
|
71
46
|
}
|
|
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
47
|
try {
|
|
157
|
-
return
|
|
48
|
+
return {
|
|
49
|
+
...DEFAULT_USER_APP_PREFERENCES,
|
|
50
|
+
...JSON.parse(raw),
|
|
51
|
+
};
|
|
158
52
|
}
|
|
159
|
-
catch {
|
|
160
|
-
console.warn("ClientStateManager:
|
|
161
|
-
return
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.warn("ClientStateManager: Problem retrieving user app preferences: ", e);
|
|
55
|
+
return DEFAULT_USER_APP_PREFERENCES;
|
|
162
56
|
}
|
|
163
57
|
}
|
|
164
58
|
/**
|
|
165
|
-
* Persist
|
|
59
|
+
* Persist user app preferences.
|
|
166
60
|
*/
|
|
167
|
-
async
|
|
168
|
-
await this.set(CLIENT_STATE_KEYS.
|
|
61
|
+
async setUserAppPreferences(preferences) {
|
|
62
|
+
await this.set(CLIENT_STATE_KEYS.USER_APP_PREFERENCES, JSON.stringify(preferences));
|
|
169
63
|
}
|
|
170
64
|
/**
|
|
171
|
-
*
|
|
172
|
-
* hydrate).
|
|
65
|
+
* Persist user app preferences.
|
|
173
66
|
*/
|
|
174
|
-
async
|
|
175
|
-
await this.
|
|
67
|
+
async resetUserAppPreferences() {
|
|
68
|
+
await this.set(CLIENT_STATE_KEYS.USER_APP_PREFERENCES, JSON.stringify(DEFAULT_USER_APP_PREFERENCES));
|
|
176
69
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// async getInvoiceDefaults(): Promise<InvoiceDefaults | null> {
|
|
183
|
-
// const raw = await this.get(CLIENT_STATE_KEYS.INVOICE_DEFAULTS);
|
|
184
|
-
// if (raw === null) return null;
|
|
185
|
-
// try {
|
|
186
|
-
// return JSON.parse(raw) as InvoiceDefaults;
|
|
187
|
-
// } catch {
|
|
188
|
-
// console.warn(
|
|
189
|
-
// "ClientStateManager: malformed invoice defaults — discarding.",
|
|
190
|
-
// );
|
|
191
|
-
// return null;
|
|
192
|
-
// }
|
|
193
|
-
// }
|
|
194
|
-
// /**
|
|
195
|
-
// * Persist user-configured invoice defaults.
|
|
196
|
-
// */
|
|
197
|
-
// async setInvoiceDefaults(defaults: InvoiceDefaults): Promise<void> {
|
|
198
|
-
// await this.set(
|
|
199
|
-
// CLIENT_STATE_KEYS.INVOICE_DEFAULTS,
|
|
200
|
-
// JSON.stringify(defaults),
|
|
201
|
-
// );
|
|
202
|
-
// }
|
|
203
|
-
// /**
|
|
204
|
-
// * Remove the persisted invoice defaults.
|
|
205
|
-
// */
|
|
206
|
-
// async removeInvoiceDefaults(): Promise<void> {
|
|
207
|
-
// await this.remove(CLIENT_STATE_KEYS.INVOICE_DEFAULTS);
|
|
208
|
-
// }
|
|
209
|
-
// async currentInvoiceNumber(): Promise<number> {
|
|
210
|
-
// const current = await this.getInvoiceDefaults();
|
|
211
|
-
// const counter = current?.invoiceNumberCounter ?? 0;
|
|
212
|
-
// return counter;
|
|
213
|
-
// }
|
|
214
|
-
// async incrementInvoiceNumber(): Promise<number> {
|
|
215
|
-
// // 1. Get current defaults
|
|
216
|
-
// const current = await this.getInvoiceDefaults();
|
|
217
|
-
// // 2. Initialize safely if missing
|
|
218
|
-
// const counter = current?.invoiceNumberCounter ?? 0;
|
|
219
|
-
// const updated: InvoiceDefaults = {
|
|
220
|
-
// ...(current ?? {
|
|
221
|
-
// currency: "PHP" as any, // fallback — adjust if you have a real default
|
|
222
|
-
// }),
|
|
223
|
-
// invoiceNumberCounter: counter + 1,
|
|
224
|
-
// };
|
|
225
|
-
// // 3. Persist
|
|
226
|
-
// await this.setInvoiceDefaults(updated);
|
|
227
|
-
// // 4. Return the new value (useful for generating invoice number)
|
|
228
|
-
// return updated.invoiceNumberCounter!;
|
|
229
|
-
// }
|
|
230
|
-
// async decrementInvoiceNumber(): Promise<number> {
|
|
231
|
-
// // 1. Get current defaults
|
|
232
|
-
// const current = await this.getInvoiceDefaults();
|
|
233
|
-
// // 2. Initialize safely if missing
|
|
234
|
-
// const counter = current?.invoiceNumberCounter ?? 0;
|
|
235
|
-
// const updated: InvoiceDefaults = {
|
|
236
|
-
// ...(current ?? {
|
|
237
|
-
// currency: "PHP" as any, // fallback — adjust if you have a real default
|
|
238
|
-
// }),
|
|
239
|
-
// invoiceNumberCounter: counter - 1,
|
|
240
|
-
// };
|
|
241
|
-
// // 3. Persist
|
|
242
|
-
// await this.setInvoiceDefaults(updated);
|
|
243
|
-
// // 4. Return the new value (useful for generating invoice number)
|
|
244
|
-
// return updated.invoiceNumberCounter!;
|
|
245
|
-
// }
|
|
246
|
-
// ── Async count ───────────────────────────────────────────────────────────
|
|
247
|
-
async count() {
|
|
248
|
-
return this._adapter.count();
|
|
70
|
+
/**
|
|
71
|
+
* Remove user app preferences.
|
|
72
|
+
*/
|
|
73
|
+
async removeUserAppPreferences() {
|
|
74
|
+
await this.remove(CLIENT_STATE_KEYS.USER_APP_PREFERENCES);
|
|
249
75
|
}
|
|
250
76
|
}
|
|
77
|
+
export const DEFAULT_USER_APP_PREFERENCES = {
|
|
78
|
+
general: {},
|
|
79
|
+
privacy: {
|
|
80
|
+
shareAnalytics: true,
|
|
81
|
+
},
|
|
82
|
+
security: {
|
|
83
|
+
key: {
|
|
84
|
+
autoLockOnMinimize: false,
|
|
85
|
+
onetimeUnlock: true,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -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;
|
|
@@ -23,9 +23,6 @@ export class MajikContactDirectory {
|
|
|
23
23
|
!contact?.mlKey) {
|
|
24
24
|
throw new MajikContactDirectoryError("Invalid contact");
|
|
25
25
|
}
|
|
26
|
-
if (!(contact instanceof MajikContact)) {
|
|
27
|
-
throw new MajikContactDirectoryError("Invalid contact instance");
|
|
28
|
-
}
|
|
29
26
|
if (this.contacts.has(contact.id)) {
|
|
30
27
|
throw new MajikContactDirectoryError(`Contact with id "${contact.id}" already exists`);
|
|
31
28
|
}
|
|
@@ -81,13 +78,13 @@ export class MajikContactDirectory {
|
|
|
81
78
|
* Get contact by public key (base64)
|
|
82
79
|
* Uses MajikContact.getPublicKeyBase64() for canonical comparison
|
|
83
80
|
*/
|
|
84
|
-
async
|
|
85
|
-
if (!
|
|
86
|
-
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");
|
|
87
84
|
}
|
|
88
85
|
for (const contact of this.contacts.values()) {
|
|
89
86
|
const contactKey = await contact.getPublicKeyBase64();
|
|
90
|
-
if (contactKey ===
|
|
87
|
+
if (contactKey === address) {
|
|
91
88
|
return contact;
|
|
92
89
|
}
|
|
93
90
|
}
|
|
@@ -124,11 +121,11 @@ export class MajikContactDirectory {
|
|
|
124
121
|
/**
|
|
125
122
|
* Checks if a contact exists by their public key (base64)
|
|
126
123
|
*/
|
|
127
|
-
async
|
|
128
|
-
if (!
|
|
129
|
-
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");
|
|
130
127
|
}
|
|
131
|
-
const contact = await this.
|
|
128
|
+
const contact = await this.getContactByAddress(address);
|
|
132
129
|
return contact !== undefined;
|
|
133
130
|
}
|
|
134
131
|
clear() {
|
|
@@ -2,11 +2,10 @@ import { MajikContact, MajikContactData, MajikContactGroup, MajikContactGroupMet
|
|
|
2
2
|
import { MajikContactDirectory } from "./majik-contact-directory";
|
|
3
3
|
import { MajikContactGroupManager } from "./majik-contact-groups";
|
|
4
4
|
import { MAJIK_API_RESPONSE } from "../types";
|
|
5
|
-
import {
|
|
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 {
|
|
9
|
-
import { ExpectedSigner } from "@majikah/majik-signature";
|
|
8
|
+
import { MajikKeyAddress } from "@majikah/majik-key";
|
|
10
9
|
export interface MajikContactManagerAdapters {
|
|
11
10
|
contacts?: MajikContactStorageAdapter;
|
|
12
11
|
groups?: MajikContactGroupStorageAdapter;
|
|
@@ -84,14 +83,12 @@ export declare class MajikContactManager {
|
|
|
84
83
|
clear(): Promise<this>;
|
|
85
84
|
getContact(id: string): MajikContact | undefined;
|
|
86
85
|
getContactByFingerprint(fingerprint: string): MajikContact | undefined;
|
|
87
|
-
|
|
86
|
+
getContactByAddress(address: MajikKeyAddress): Promise<MajikContact | undefined>;
|
|
88
87
|
getContactsByIds(ids: string[], strict?: boolean): MajikContact[];
|
|
89
88
|
getContactsByPublicKeys(publicKeys: string[], strict?: boolean): Promise<MajikContact[]>;
|
|
90
|
-
getMajikRecipients(mode: ContactManagerQueryMode | undefined, input: string[], strict?: boolean): Promise<MajikRecipient[]>;
|
|
91
|
-
getExpectedSigners(mode: ContactManagerQueryMode | undefined, input: string[], strict?: boolean): Promise<ExpectedSigner[]>;
|
|
92
89
|
hasContact(id: string): boolean;
|
|
93
90
|
hasFingerprint(fingerprint: string): boolean;
|
|
94
|
-
|
|
91
|
+
hasContactByAddress(address: MajikKeyAddress): Promise<boolean>;
|
|
95
92
|
listContacts(sortedByLabel?: boolean, majikahOnly?: boolean): MajikContact[];
|
|
96
93
|
isMajikahRegistered(id: string): boolean;
|
|
97
94
|
isMajikahIdentityChecked(id: string): boolean;
|
|
@@ -2,12 +2,11 @@ import { MajikContact, } from "@majikah/majik-contact";
|
|
|
2
2
|
import { MajikContactDirectory } from "./majik-contact-directory";
|
|
3
3
|
import { MajikContactGroupManager } from "./majik-contact-groups";
|
|
4
4
|
import { MajikContactManagerError } from "./errors";
|
|
5
|
-
import { arrayBufferToBase64, arrayToBase64, base64ToArrayBuffer,
|
|
5
|
+
import { arrayBufferToBase64, arrayToBase64, base64ToArrayBuffer, } from "../utils/utilities";
|
|
6
6
|
import { KEY_ALGO } from "../crypto/constants";
|
|
7
7
|
import { gunzipSync, gzipSync } from "fflate";
|
|
8
8
|
import { InMemoryContactAdapter } from "../storage/contact-directory/contacts/adapter-memory";
|
|
9
9
|
import { InMemoryContactGroupAdapter } from "../storage/contact-directory/groups/adapter-memory";
|
|
10
|
-
import { MajikEnvelope } from "@majikah/majik-envelope";
|
|
11
10
|
export class MajikContactManager {
|
|
12
11
|
directory;
|
|
13
12
|
groupManager;
|
|
@@ -191,8 +190,8 @@ export class MajikContactManager {
|
|
|
191
190
|
getContactByFingerprint(fingerprint) {
|
|
192
191
|
return this.directory.getContactByFingerprint(fingerprint);
|
|
193
192
|
}
|
|
194
|
-
async
|
|
195
|
-
return await this.directory.
|
|
193
|
+
async getContactByAddress(address) {
|
|
194
|
+
return await this.directory.getContactByAddress(address);
|
|
196
195
|
}
|
|
197
196
|
getContactsByIds(ids, strict = false) {
|
|
198
197
|
if (!ids?.length)
|
|
@@ -219,7 +218,7 @@ export class MajikContactManager {
|
|
|
219
218
|
return [];
|
|
220
219
|
const uniqueKeys = [...new Set(publicKeys)];
|
|
221
220
|
const contacts = await Promise.all(uniqueKeys.map(async (key) => {
|
|
222
|
-
const contact = await this.directory.
|
|
221
|
+
const contact = await this.directory.getContactByAddress(key);
|
|
223
222
|
if (!contact && strict) {
|
|
224
223
|
throw new MajikContactManagerError(`Contact not found for publicKey: ${key}`);
|
|
225
224
|
}
|
|
@@ -227,80 +226,14 @@ export class MajikContactManager {
|
|
|
227
226
|
}));
|
|
228
227
|
return contacts.filter((c) => Boolean(c));
|
|
229
228
|
}
|
|
230
|
-
async getMajikRecipients(mode = "id", input, strict) {
|
|
231
|
-
if (!input?.length)
|
|
232
|
-
throw new MajikContactManagerError("At least 1 id/key is required");
|
|
233
|
-
const contacts = mode === "public_key"
|
|
234
|
-
? await this.getContactsByPublicKeys(input, strict)
|
|
235
|
-
: this.getContactsByIds(input, strict);
|
|
236
|
-
if (!contacts || contacts.length === 0)
|
|
237
|
-
return [];
|
|
238
|
-
const recipients = [];
|
|
239
|
-
const seen = new Set();
|
|
240
|
-
const invalidContacts = [];
|
|
241
|
-
for (const contact of contacts) {
|
|
242
|
-
if (!contact)
|
|
243
|
-
continue;
|
|
244
|
-
// dedupe by fingerprint
|
|
245
|
-
if (seen.has(contact.fingerprint))
|
|
246
|
-
continue;
|
|
247
|
-
const mlPubKey = base64ToUint8Array(contact.mlKey);
|
|
248
|
-
if (!mlPubKey) {
|
|
249
|
-
invalidContacts.push(contact.fingerprint);
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
const builtMajikRecipient = await MajikEnvelope.buildMajikRecipientFromContact(contact);
|
|
253
|
-
recipients.push(builtMajikRecipient);
|
|
254
|
-
seen.add(contact.fingerprint);
|
|
255
|
-
}
|
|
256
|
-
if (invalidContacts.length > 0) {
|
|
257
|
-
throw new MajikContactManagerError(`Invalid ML-KEM public key for contact(s): ${invalidContacts.join(", ")}`);
|
|
258
|
-
}
|
|
259
|
-
return recipients;
|
|
260
|
-
}
|
|
261
|
-
async getExpectedSigners(mode = "id", input, strict) {
|
|
262
|
-
if (!input?.length)
|
|
263
|
-
throw new MajikContactManagerError("At least 1 id/key is required");
|
|
264
|
-
const contacts = mode === "public_key"
|
|
265
|
-
? await this.getContactsByPublicKeys(input, strict)
|
|
266
|
-
: this.getContactsByIds(input, strict);
|
|
267
|
-
if (!contacts || contacts.length === 0)
|
|
268
|
-
return [];
|
|
269
|
-
const signers = [];
|
|
270
|
-
const seen = new Set();
|
|
271
|
-
const invalidContacts = [];
|
|
272
|
-
for (const contact of contacts) {
|
|
273
|
-
if (!contact)
|
|
274
|
-
continue;
|
|
275
|
-
// dedupe by fingerprint
|
|
276
|
-
if (seen.has(contact.fingerprint))
|
|
277
|
-
continue;
|
|
278
|
-
const mlDsaPublicKey = contact.mlDsaPublicKeyBase64;
|
|
279
|
-
if (!mlDsaPublicKey?.trim()) {
|
|
280
|
-
invalidContacts.push(contact.fingerprint);
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
const expectedSigner = {
|
|
284
|
-
edPublicKey: contact.edPublicKeyBase64,
|
|
285
|
-
mlDsaPublicKey: contact.mlDsaPublicKeyBase64,
|
|
286
|
-
signerId: contact.fingerprint,
|
|
287
|
-
};
|
|
288
|
-
signers.push(expectedSigner);
|
|
289
|
-
seen.add(contact.fingerprint);
|
|
290
|
-
}
|
|
291
|
-
if (invalidContacts.length > 0) {
|
|
292
|
-
throw new MajikContactManagerError(`Invalid ML-KEM public key for contact(s): ${invalidContacts.join(", ")}`);
|
|
293
|
-
}
|
|
294
|
-
return signers;
|
|
295
|
-
}
|
|
296
229
|
hasContact(id) {
|
|
297
230
|
return this.directory.hasContact(id);
|
|
298
231
|
}
|
|
299
232
|
hasFingerprint(fingerprint) {
|
|
300
233
|
return this.directory.hasFingerprint(fingerprint);
|
|
301
234
|
}
|
|
302
|
-
async
|
|
303
|
-
return this.directory.
|
|
235
|
+
async hasContactByAddress(address) {
|
|
236
|
+
return this.directory.hasContactByAddress(address);
|
|
304
237
|
}
|
|
305
238
|
listContacts(sortedByLabel = false, majikahOnly = false) {
|
|
306
239
|
return this.directory.listContacts(sortedByLabel, majikahOnly);
|
|
@@ -18,13 +18,49 @@ export interface ClientStateEntry {
|
|
|
18
18
|
}
|
|
19
19
|
export declare const CLIENT_STATE_KEYS: {
|
|
20
20
|
readonly ACCOUNT_ORDER: "user_account_order";
|
|
21
|
+
readonly USER_APP_PREFERENCES: "user_app_preferences";
|
|
21
22
|
};
|
|
22
23
|
export type ClientStateKey = (typeof CLIENT_STATE_KEYS)[keyof typeof CLIENT_STATE_KEYS];
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
* account. Stored as a JSON array: `["id1", "id2", ...]`.
|
|
25
|
+
* User-configured app-wide preferences.
|
|
26
26
|
*/
|
|
27
|
-
export
|
|
27
|
+
export interface UserAppPreferences {
|
|
28
|
+
general: GeneralPreferences;
|
|
29
|
+
privacy: PrivacyPreferences;
|
|
30
|
+
security: SecurityPreferences;
|
|
31
|
+
}
|
|
32
|
+
export interface GeneralPreferences {
|
|
33
|
+
history?: HistoryPreferences;
|
|
34
|
+
}
|
|
35
|
+
export interface HistoryPreferences {
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
maxCount?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface SigningPreferences {
|
|
40
|
+
autoSeal?: boolean;
|
|
41
|
+
defaultToTSA?: boolean;
|
|
42
|
+
defaultToDetached?: boolean;
|
|
43
|
+
autosave?: {
|
|
44
|
+
afterSign?: AutosavePreferences;
|
|
45
|
+
afterSeal?: AutosavePreferences;
|
|
46
|
+
afterNotary?: AutosavePreferences;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export interface AutosavePreferences {
|
|
50
|
+
enabled?: boolean;
|
|
51
|
+
defaultPath?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface PrivacyPreferences {
|
|
54
|
+
shareAnalytics?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface SecurityPreferences {
|
|
57
|
+
key?: KeyPreferences;
|
|
58
|
+
}
|
|
59
|
+
export interface KeyPreferences {
|
|
60
|
+
autoLockOnMinimize?: boolean;
|
|
61
|
+
autoLockInterval?: number;
|
|
62
|
+
onetimeUnlock?: boolean;
|
|
63
|
+
}
|
|
28
64
|
/**
|
|
29
65
|
* Pluggable persistence backend for client-level state.
|
|
30
66
|
*
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* ClientStateManager owns all serialisation / deserialisation logic; the
|
|
8
8
|
* adapter only moves bytes.
|
|
9
9
|
*/
|
|
10
|
+
import { BASE_CLIENT_STATE_KEYS } from "@majikah/majik-key-client";
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Well-known state keys
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
export const CLIENT_STATE_KEYS = {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
...BASE_CLIENT_STATE_KEYS,
|
|
16
|
+
USER_APP_PREFERENCES: "user_app_preferences",
|
|
16
17
|
};
|