@majikah/majik-signature-client 0.2.0 → 0.3.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/backup/constants.d.ts +4 -4
- package/dist/core/backup/constants.js +9 -2
- package/dist/core/backup/types.d.ts +2 -1
- package/dist/core/client-state-manager.js +5 -0
- package/dist/core/stamp/majik-signature-stamp-manager.d.ts +37 -0
- package/dist/core/stamp/majik-signature-stamp-manager.js +159 -0
- package/dist/core/stamp/majik-signature-stamp.d.ts +52 -0
- package/dist/core/stamp/majik-signature-stamp.js +119 -0
- package/dist/core/storage/client-state/_types.d.ts +8 -0
- package/dist/core/storage/index.d.ts +4 -0
- package/dist/core/storage/index.js +3 -0
- package/dist/core/storage/sql-schema.d.ts +2 -0
- package/dist/core/storage/sql-schema.js +27 -0
- package/dist/core/storage/stamps/_types.d.ts +3 -0
- package/dist/core/storage/stamps/_types.js +1 -0
- package/dist/core/storage/stamps/adapter-idb.d.ts +3 -0
- package/dist/core/storage/stamps/adapter-idb.js +5 -0
- package/dist/core/storage/stamps/adapter-memory.d.ts +14 -0
- package/dist/core/storage/stamps/adapter-memory.js +32 -0
- package/dist/core/storage/stamps/adapter-sql.d.ts +18 -0
- package/dist/core/storage/stamps/adapter-sql.js +99 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/majik-signature-client.d.ts +38 -5
- package/dist/majik-signature-client.js +137 -45
- package/package.json +2 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* MJKC = MajiK Contacts
|
|
7
7
|
* MJKA = MajiK App-data
|
|
8
8
|
*/
|
|
9
|
-
export declare const
|
|
10
|
-
readonly
|
|
9
|
+
export declare const MAJIK_SIGNATURE_BACKUP_MAGIC: {
|
|
10
|
+
readonly stamps: Uint8Array;
|
|
11
11
|
readonly contacts: Uint8Array;
|
|
12
12
|
readonly appData: Uint8Array;
|
|
13
13
|
};
|
|
14
14
|
/** The union of valid magic-byte headers. */
|
|
15
|
-
export type MajikMessageBackupMagic = (typeof
|
|
15
|
+
export type MajikMessageBackupMagic = (typeof MAJIK_SIGNATURE_BACKUP_MAGIC)[keyof typeof MAJIK_SIGNATURE_BACKUP_MAGIC];
|
|
16
16
|
/** Which backup type a file contains, as a discriminant string. */
|
|
17
|
-
export type MajikMessageBackupType = keyof typeof
|
|
17
|
+
export type MajikMessageBackupType = keyof typeof MAJIK_SIGNATURE_BACKUP_MAGIC;
|
|
18
18
|
/** Byte length of every backup file header (4-byte tag + 2-byte version). */
|
|
19
19
|
export declare const MAJIK_MESSAGE_BACKUP_MAGIC_SIZE = 6;
|
|
@@ -6,8 +6,15 @@
|
|
|
6
6
|
* MJKC = MajiK Contacts
|
|
7
7
|
* MJKA = MajiK App-data
|
|
8
8
|
*/
|
|
9
|
-
export const
|
|
10
|
-
|
|
9
|
+
export const MAJIK_SIGNATURE_BACKUP_MAGIC = {
|
|
10
|
+
stamps: new Uint8Array([
|
|
11
|
+
0x53,
|
|
12
|
+
0x54,
|
|
13
|
+
0x4d,
|
|
14
|
+
0x50, // "STMP"
|
|
15
|
+
0x00,
|
|
16
|
+
0x01, // Version 1
|
|
17
|
+
]),
|
|
11
18
|
contacts: new Uint8Array([0x4d, 0x4a, 0x4b, 0x43, 0x00, 0x01]), // "MJKC" v1
|
|
12
19
|
appData: new Uint8Array([0x4d, 0x4a, 0x4b, 0x41, 0x00, 0x01]), // "MJKA" v1
|
|
13
20
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MajikContact, MajikContactGroup } from "@majikah/majik-contact";
|
|
2
2
|
import { MajikContactManagerJSON } from "../contacts/types";
|
|
3
3
|
import { UserAppPreferences } from "../storage";
|
|
4
|
+
import { MajikSignatureStampJSON } from "../stamp/majik-signature-stamp";
|
|
4
5
|
export interface ContactManagerSnapshot {
|
|
5
6
|
/** Raw JSON payload — used internally by restoreContacts for bulk writes */
|
|
6
7
|
managerJSON: MajikContactManagerJSON;
|
|
@@ -12,7 +13,7 @@ export interface ContactManagerSnapshot {
|
|
|
12
13
|
export interface AppDataSnapshot {
|
|
13
14
|
contacts: MajikContact[];
|
|
14
15
|
groups: MajikContactGroup[];
|
|
16
|
+
stamps: MajikSignatureStampJSON[];
|
|
15
17
|
preferences: UserAppPreferences | null;
|
|
16
|
-
/** @internal Raw manager JSON — used by restoreAppDataSelective, not for display */
|
|
17
18
|
_contactsManagerJSON: MajikContactManagerJSON;
|
|
18
19
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MajikFileIdentity } from "@majikah/majik-file";
|
|
2
|
+
import { MajikSignatureStamp, MajikSignatureStampJSON, StampAssetKind, CreateStampOptions } from "./majik-signature-stamp";
|
|
3
|
+
import { StorageQuery } from "../storage/storage-adapter";
|
|
4
|
+
import { MajikSignatureStampStorageAdapter } from "../storage";
|
|
5
|
+
export declare class MajikSignatureStampManagerError extends Error {
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
constructor(message: string, cause?: unknown);
|
|
8
|
+
}
|
|
9
|
+
export declare class MajikSignatureStampManager {
|
|
10
|
+
private readonly _cache;
|
|
11
|
+
private _adapter;
|
|
12
|
+
constructor(adapter?: MajikSignatureStampStorageAdapter);
|
|
13
|
+
get adapter(): MajikSignatureStampStorageAdapter;
|
|
14
|
+
setAdapter(adapter: MajikSignatureStampStorageAdapter): void;
|
|
15
|
+
hydrate(): Promise<void>;
|
|
16
|
+
toJSON(): Promise<MajikSignatureStampJSON[]>;
|
|
17
|
+
bulkRestoreFromJSON(data: MajikSignatureStampJSON[]): Promise<void>;
|
|
18
|
+
static fromJSON(data: MajikSignatureStampJSON[], adapter?: MajikSignatureStampStorageAdapter): Promise<MajikSignatureStampManager>;
|
|
19
|
+
private _persist;
|
|
20
|
+
create(options: CreateStampOptions): Promise<MajikSignatureStamp>;
|
|
21
|
+
save(stamp: MajikSignatureStamp): Promise<void>;
|
|
22
|
+
load(id: string): Promise<MajikSignatureStamp | null>;
|
|
23
|
+
getStampOrThrow(id: string): Promise<MajikSignatureStamp>;
|
|
24
|
+
loadAll(): Promise<MajikSignatureStamp[]>;
|
|
25
|
+
delete(id: string): Promise<void>;
|
|
26
|
+
has(id: string): Promise<boolean>;
|
|
27
|
+
get(id: string): MajikSignatureStamp | undefined;
|
|
28
|
+
list(): MajikSignatureStamp[];
|
|
29
|
+
listByKind(kind: StampAssetKind): MajikSignatureStamp[];
|
|
30
|
+
listByFingerprint(fingerprint: string): MajikSignatureStamp[];
|
|
31
|
+
query(query: StorageQuery<MajikSignatureStampJSON>): Promise<MajikSignatureStampJSON[]>;
|
|
32
|
+
rename(id: string, newName: string): Promise<MajikSignatureStamp>;
|
|
33
|
+
/** Write-once at the MajikFile layer — id is preserved explicitly. */
|
|
34
|
+
replaceContent(id: string, rawBytes: Uint8Array, identity: MajikFileIdentity, mimeType?: string): Promise<MajikSignatureStamp>;
|
|
35
|
+
decryptContent(id: string, identity: Pick<MajikFileIdentity, "fingerprint" | "mlKemSecretKey">): Promise<Uint8Array>;
|
|
36
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { MajikSignatureStamp, } from "./majik-signature-stamp";
|
|
2
|
+
import { InMemoryStampstoreAdapter, } from "../storage";
|
|
3
|
+
export class MajikSignatureStampManagerError extends Error {
|
|
4
|
+
cause;
|
|
5
|
+
constructor(message, cause) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "MajikSignatureStampManagerError";
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class MajikSignatureStampManager {
|
|
12
|
+
_cache = new Map();
|
|
13
|
+
_adapter;
|
|
14
|
+
constructor(adapter) {
|
|
15
|
+
this._adapter = adapter ?? new InMemoryStampstoreAdapter();
|
|
16
|
+
}
|
|
17
|
+
get adapter() {
|
|
18
|
+
return this._adapter;
|
|
19
|
+
}
|
|
20
|
+
setAdapter(adapter) {
|
|
21
|
+
this._adapter = adapter;
|
|
22
|
+
}
|
|
23
|
+
// ── Hydration ─────────────────────────────────────────────────────────
|
|
24
|
+
async hydrate() {
|
|
25
|
+
this._cache.clear();
|
|
26
|
+
const all = await this._adapter.list();
|
|
27
|
+
for (const json of all) {
|
|
28
|
+
try {
|
|
29
|
+
const stamp = MajikSignatureStamp.fromJSON(json);
|
|
30
|
+
this._cache.set(stamp.id, stamp);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
console.warn(`MajikSignatureStampManager.hydrate: skipping malformed stamp "${json?.id}":`, err);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// ── Serialization ─────────────────────────────────────────────────────
|
|
38
|
+
async toJSON() {
|
|
39
|
+
return this._adapter.list();
|
|
40
|
+
}
|
|
41
|
+
async bulkRestoreFromJSON(data) {
|
|
42
|
+
if (!Array.isArray(data)) {
|
|
43
|
+
throw new MajikSignatureStampManagerError("bulkRestoreFromJSON: expected MajikSignatureStampJSON[]");
|
|
44
|
+
}
|
|
45
|
+
await this._adapter.bulkSave(data);
|
|
46
|
+
await this.hydrate();
|
|
47
|
+
}
|
|
48
|
+
static async fromJSON(data, adapter) {
|
|
49
|
+
const manager = new MajikSignatureStampManager(adapter);
|
|
50
|
+
await manager.bulkRestoreFromJSON(data);
|
|
51
|
+
return manager;
|
|
52
|
+
}
|
|
53
|
+
async _persist(stamp) {
|
|
54
|
+
await this._adapter.save(stamp.toJSON());
|
|
55
|
+
}
|
|
56
|
+
// ── Core CRUD ───────────────────────────────────────────────────────────
|
|
57
|
+
async create(options) {
|
|
58
|
+
const stamp = await MajikSignatureStamp.create(options);
|
|
59
|
+
await this._persist(stamp);
|
|
60
|
+
this._cache.set(stamp.id, stamp);
|
|
61
|
+
return stamp;
|
|
62
|
+
}
|
|
63
|
+
async save(stamp) {
|
|
64
|
+
await this._persist(stamp);
|
|
65
|
+
this._cache.set(stamp.id, stamp);
|
|
66
|
+
}
|
|
67
|
+
async load(id) {
|
|
68
|
+
const cached = this._cache.get(id);
|
|
69
|
+
if (cached)
|
|
70
|
+
return cached;
|
|
71
|
+
const json = await this._adapter.getById(id);
|
|
72
|
+
if (!json)
|
|
73
|
+
return null;
|
|
74
|
+
const stamp = MajikSignatureStamp.fromJSON(json);
|
|
75
|
+
this._cache.set(stamp.id, stamp);
|
|
76
|
+
return stamp;
|
|
77
|
+
}
|
|
78
|
+
async getStampOrThrow(id) {
|
|
79
|
+
const stamp = await this.load(id);
|
|
80
|
+
if (!stamp)
|
|
81
|
+
throw new MajikSignatureStampManagerError(`Stamp not found: ${id}`);
|
|
82
|
+
return stamp;
|
|
83
|
+
}
|
|
84
|
+
async loadAll() {
|
|
85
|
+
const all = await this._adapter.list();
|
|
86
|
+
for (const json of all) {
|
|
87
|
+
if (!this._cache.has(json.id)) {
|
|
88
|
+
try {
|
|
89
|
+
const stamp = MajikSignatureStamp.fromJSON(json);
|
|
90
|
+
this._cache.set(stamp.id, stamp);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.warn(`MajikSignatureStampManager.loadAll: skipping malformed stamp "${json?.id}":`, err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [...this._cache.values()];
|
|
98
|
+
}
|
|
99
|
+
async delete(id) {
|
|
100
|
+
await this._adapter.remove(id);
|
|
101
|
+
this._cache.delete(id);
|
|
102
|
+
}
|
|
103
|
+
async has(id) {
|
|
104
|
+
if (this._cache.has(id))
|
|
105
|
+
return true;
|
|
106
|
+
return this._adapter.exists(id);
|
|
107
|
+
}
|
|
108
|
+
get(id) {
|
|
109
|
+
return this._cache.get(id);
|
|
110
|
+
}
|
|
111
|
+
list() {
|
|
112
|
+
return [...this._cache.values()];
|
|
113
|
+
}
|
|
114
|
+
listByKind(kind) {
|
|
115
|
+
return this.list().filter((s) => s.kind === kind);
|
|
116
|
+
}
|
|
117
|
+
listByFingerprint(fingerprint) {
|
|
118
|
+
return this.list().filter((s) => s.fingerprint === fingerprint);
|
|
119
|
+
}
|
|
120
|
+
async query(query) {
|
|
121
|
+
if (!this._adapter.query) {
|
|
122
|
+
throw new MajikSignatureStampManagerError("query: current adapter does not support query()");
|
|
123
|
+
}
|
|
124
|
+
return this._adapter.query(query);
|
|
125
|
+
}
|
|
126
|
+
// ── Mutations ───────────────────────────────────────────────────────────
|
|
127
|
+
async rename(id, newName) {
|
|
128
|
+
const stamp = await this.getStampOrThrow(id);
|
|
129
|
+
stamp.rename(newName);
|
|
130
|
+
await this._persist(stamp);
|
|
131
|
+
return stamp;
|
|
132
|
+
}
|
|
133
|
+
/** Write-once at the MajikFile layer — id is preserved explicitly. */
|
|
134
|
+
async replaceContent(id, rawBytes, identity, mimeType) {
|
|
135
|
+
const existing = await this.getStampOrThrow(id);
|
|
136
|
+
const replacement = await MajikSignatureStamp.create({
|
|
137
|
+
id: existing.id,
|
|
138
|
+
data: rawBytes,
|
|
139
|
+
identity,
|
|
140
|
+
kind: existing.kind,
|
|
141
|
+
name: existing.name,
|
|
142
|
+
mimeType: mimeType ?? existing.mimeType ?? undefined,
|
|
143
|
+
});
|
|
144
|
+
await this._persist(replacement);
|
|
145
|
+
this._cache.set(replacement.id, replacement);
|
|
146
|
+
return replacement;
|
|
147
|
+
}
|
|
148
|
+
async decryptContent(id, identity) {
|
|
149
|
+
const stamp = await this.getStampOrThrow(id);
|
|
150
|
+
return stamp.decryptContent(identity);
|
|
151
|
+
}
|
|
152
|
+
async bulkRemove(ids) {
|
|
153
|
+
if (ids.length === 0)
|
|
154
|
+
return;
|
|
155
|
+
await this._adapter.bulkRemove(ids);
|
|
156
|
+
for (const id of ids)
|
|
157
|
+
this._cache.delete(id);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MajikFileIdentity } from "@majikah/majik-file";
|
|
2
|
+
export type StampAssetKind = "image" | "audio" | "video" | "text";
|
|
3
|
+
export interface MajikSignatureStampJSON {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: StampAssetKind;
|
|
6
|
+
name: string;
|
|
7
|
+
mimeType: string | null;
|
|
8
|
+
sizeBytes: number;
|
|
9
|
+
fingerprint: string;
|
|
10
|
+
data: string;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
lastUpdate: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CreateStampOptions {
|
|
15
|
+
data: Uint8Array | ArrayBuffer;
|
|
16
|
+
identity: MajikFileIdentity;
|
|
17
|
+
kind: StampAssetKind;
|
|
18
|
+
name: string;
|
|
19
|
+
mimeType?: string;
|
|
20
|
+
/** Pass the existing id back in when replacing content on a stamp that
|
|
21
|
+
* already exists — MajikFile is write-once, so this is a *new* encrypted
|
|
22
|
+
* artifact under the hood, but the id stays stable for the caller. */
|
|
23
|
+
id?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class MajikSignatureStamp {
|
|
26
|
+
private readonly _id;
|
|
27
|
+
private readonly _kind;
|
|
28
|
+
private _name;
|
|
29
|
+
private readonly _mimeType;
|
|
30
|
+
private readonly _sizeBytes;
|
|
31
|
+
private readonly _fingerprint;
|
|
32
|
+
private readonly _timestamp;
|
|
33
|
+
private _lastUpdate;
|
|
34
|
+
private _encryptedBinary;
|
|
35
|
+
private constructor();
|
|
36
|
+
get id(): string;
|
|
37
|
+
get kind(): StampAssetKind;
|
|
38
|
+
get name(): string;
|
|
39
|
+
get mimeType(): string | null;
|
|
40
|
+
get sizeBytes(): number;
|
|
41
|
+
get fingerprint(): string;
|
|
42
|
+
get timestamp(): string;
|
|
43
|
+
get lastUpdate(): string;
|
|
44
|
+
static create(options: CreateStampOptions): Promise<MajikSignatureStamp>;
|
|
45
|
+
/** Plaintext-only rename — no re-encryption, no MajikFile involvement. */
|
|
46
|
+
rename(newName: string): void;
|
|
47
|
+
toJSON(): MajikSignatureStampJSON;
|
|
48
|
+
static fromJSON(json: MajikSignatureStampJSON): MajikSignatureStamp;
|
|
49
|
+
/** Decrypt and return the raw plaintext content bytes (image/audio/video/text). */
|
|
50
|
+
decryptContent(identity: Pick<MajikFileIdentity, "fingerprint" | "mlKemSecretKey">): Promise<Uint8Array>;
|
|
51
|
+
toBinaryBytes(): Uint8Array;
|
|
52
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// majik-signature-stamp.ts
|
|
2
|
+
import { MajikFile } from "@majikah/majik-file";
|
|
3
|
+
import { arrayToBase64, base64ToUint8Array } from "../utils/utilities";
|
|
4
|
+
export class MajikSignatureStamp {
|
|
5
|
+
_id;
|
|
6
|
+
_kind;
|
|
7
|
+
_name;
|
|
8
|
+
_mimeType;
|
|
9
|
+
_sizeBytes;
|
|
10
|
+
_fingerprint;
|
|
11
|
+
_timestamp;
|
|
12
|
+
_lastUpdate;
|
|
13
|
+
_encryptedBinary; // the .mjkb bytes — never the plaintext
|
|
14
|
+
constructor(json, binary) {
|
|
15
|
+
this._id = json.id;
|
|
16
|
+
this._kind = json.kind;
|
|
17
|
+
this._name = json.name;
|
|
18
|
+
this._mimeType = json.mimeType;
|
|
19
|
+
this._sizeBytes = json.sizeBytes;
|
|
20
|
+
this._fingerprint = json.fingerprint;
|
|
21
|
+
this._timestamp = json.timestamp;
|
|
22
|
+
this._lastUpdate = json.lastUpdate;
|
|
23
|
+
this._encryptedBinary = binary;
|
|
24
|
+
}
|
|
25
|
+
get id() {
|
|
26
|
+
return this._id;
|
|
27
|
+
}
|
|
28
|
+
get kind() {
|
|
29
|
+
return this._kind;
|
|
30
|
+
}
|
|
31
|
+
get name() {
|
|
32
|
+
return this._name;
|
|
33
|
+
}
|
|
34
|
+
get mimeType() {
|
|
35
|
+
return this._mimeType;
|
|
36
|
+
}
|
|
37
|
+
get sizeBytes() {
|
|
38
|
+
return this._sizeBytes;
|
|
39
|
+
}
|
|
40
|
+
get fingerprint() {
|
|
41
|
+
return this._fingerprint;
|
|
42
|
+
}
|
|
43
|
+
get timestamp() {
|
|
44
|
+
return this._timestamp;
|
|
45
|
+
}
|
|
46
|
+
get lastUpdate() {
|
|
47
|
+
return this._lastUpdate;
|
|
48
|
+
}
|
|
49
|
+
// ── CREATE ──────────────────────────────────────────────────────────────
|
|
50
|
+
static async create(options) {
|
|
51
|
+
const { data, identity, kind, name, mimeType, id } = options;
|
|
52
|
+
if (!data)
|
|
53
|
+
throw new Error("MajikSignatureStamp.create: data is required");
|
|
54
|
+
if (!identity)
|
|
55
|
+
throw new Error("MajikSignatureStamp.create: identity is required");
|
|
56
|
+
if (!name?.trim())
|
|
57
|
+
throw new Error("MajikSignatureStamp.create: name is required");
|
|
58
|
+
const file = await MajikFile.create({
|
|
59
|
+
id,
|
|
60
|
+
data,
|
|
61
|
+
identity,
|
|
62
|
+
context: "user_upload",
|
|
63
|
+
originalName: name,
|
|
64
|
+
mimeType,
|
|
65
|
+
userId: identity.fingerprint,
|
|
66
|
+
});
|
|
67
|
+
const fileJson = file.toJSON();
|
|
68
|
+
const now = new Date().toISOString();
|
|
69
|
+
const binary = file.toBinaryBytes();
|
|
70
|
+
const json = {
|
|
71
|
+
id: fileJson.id,
|
|
72
|
+
kind,
|
|
73
|
+
name,
|
|
74
|
+
mimeType: fileJson.mime_type,
|
|
75
|
+
sizeBytes: fileJson.size_original,
|
|
76
|
+
fingerprint: identity.fingerprint,
|
|
77
|
+
data: arrayToBase64(binary),
|
|
78
|
+
timestamp: fileJson.timestamp ?? now,
|
|
79
|
+
lastUpdate: fileJson.last_update ?? now,
|
|
80
|
+
};
|
|
81
|
+
return new MajikSignatureStamp(json, binary);
|
|
82
|
+
}
|
|
83
|
+
/** Plaintext-only rename — no re-encryption, no MajikFile involvement. */
|
|
84
|
+
rename(newName) {
|
|
85
|
+
if (!newName?.trim())
|
|
86
|
+
throw new Error("rename: name must be non-empty");
|
|
87
|
+
this._name = newName.trim();
|
|
88
|
+
this._lastUpdate = new Date().toISOString();
|
|
89
|
+
}
|
|
90
|
+
// ── SERIALISATION ───────────────────────────────────────────────────────
|
|
91
|
+
toJSON() {
|
|
92
|
+
return {
|
|
93
|
+
id: this._id,
|
|
94
|
+
kind: this._kind,
|
|
95
|
+
name: this._name,
|
|
96
|
+
mimeType: this._mimeType,
|
|
97
|
+
sizeBytes: this._sizeBytes,
|
|
98
|
+
fingerprint: this._fingerprint,
|
|
99
|
+
data: arrayToBase64(this._encryptedBinary),
|
|
100
|
+
timestamp: this._timestamp,
|
|
101
|
+
lastUpdate: this._lastUpdate,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
static fromJSON(json) {
|
|
105
|
+
return new MajikSignatureStamp(json, base64ToUint8Array(json.data));
|
|
106
|
+
}
|
|
107
|
+
// ── DECRYPT ─────────────────────────────────────────────────────────────
|
|
108
|
+
/** Decrypt and return the raw plaintext content bytes (image/audio/video/text). */
|
|
109
|
+
async decryptContent(identity) {
|
|
110
|
+
if (this._fingerprint !== identity.fingerprint) {
|
|
111
|
+
throw new Error(`decryptContent: stamp was encrypted under fingerprint "${this._fingerprint}", ` +
|
|
112
|
+
`but the provided identity has fingerprint "${identity.fingerprint}"`);
|
|
113
|
+
}
|
|
114
|
+
return MajikFile.decrypt(this._encryptedBinary, identity);
|
|
115
|
+
}
|
|
116
|
+
toBinaryBytes() {
|
|
117
|
+
return this._encryptedBinary;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -33,6 +33,7 @@ export interface UserAppPreferences {
|
|
|
33
33
|
general: GeneralPreferences;
|
|
34
34
|
signing: SigningPreferences;
|
|
35
35
|
privacy: PrivacyPreferences;
|
|
36
|
+
security: SecurityPreferences;
|
|
36
37
|
}
|
|
37
38
|
export interface GeneralPreferences {
|
|
38
39
|
history?: HistoryPreferences;
|
|
@@ -47,6 +48,13 @@ export interface SigningPreferences {
|
|
|
47
48
|
export interface PrivacyPreferences {
|
|
48
49
|
shareAnalytics?: boolean;
|
|
49
50
|
}
|
|
51
|
+
export interface SecurityPreferences {
|
|
52
|
+
key?: KeyPreferences;
|
|
53
|
+
}
|
|
54
|
+
export interface KeyPreferences {
|
|
55
|
+
autoLockOnMinimize?: boolean;
|
|
56
|
+
autoLockInterval?: number;
|
|
57
|
+
}
|
|
50
58
|
/**
|
|
51
59
|
* Pluggable persistence backend for client-level state.
|
|
52
60
|
*
|
|
@@ -18,3 +18,7 @@ export * from "./keystore/adapter-idb";
|
|
|
18
18
|
export * from "./keystore/adapter-sql";
|
|
19
19
|
export * from "./keystore/adapter-memory";
|
|
20
20
|
export type * from "./keystore/_types";
|
|
21
|
+
export * from "./stamps/adapter-idb";
|
|
22
|
+
export * from "./stamps/adapter-sql";
|
|
23
|
+
export * from "./stamps/adapter-memory";
|
|
24
|
+
export type * from "./stamps/_types";
|
|
@@ -14,3 +14,6 @@ export * from "./contact-directory/groups/adapter-memory";
|
|
|
14
14
|
export * from "./keystore/adapter-idb";
|
|
15
15
|
export * from "./keystore/adapter-sql";
|
|
16
16
|
export * from "./keystore/adapter-memory";
|
|
17
|
+
export * from "./stamps/adapter-idb";
|
|
18
|
+
export * from "./stamps/adapter-sql";
|
|
19
|
+
export * from "./stamps/adapter-memory";
|
|
@@ -9,6 +9,7 @@ export declare const MAJIKAH_SQL_TABLES: {
|
|
|
9
9
|
readonly MAJIK_KEYS: "majik_keys";
|
|
10
10
|
readonly MAJIK_CONTACTS: "majik_contacts";
|
|
11
11
|
readonly MAJIK_CONTACT_GROUPS: "majik_contact_groups";
|
|
12
|
+
readonly MAJIK_SIGNATURE_STAMPS: "majik_signature_stamps";
|
|
12
13
|
};
|
|
13
14
|
export type MajikahSQLTable = (typeof MAJIKAH_SQL_TABLES)[keyof typeof MAJIKAH_SQL_TABLES];
|
|
14
15
|
export declare function buildSchemaSQL(schemas: MajikahSQLSchema[]): MajikahSQLSchema;
|
|
@@ -16,5 +17,6 @@ export declare const MAJIKAH_SQL_SCHEMA_MAJIK_CLIENT_STATE: MajikahSQLSchema;
|
|
|
16
17
|
export declare const MAJIKAH_SQL_SCHEMA_MAJIK_KEYS: MajikahSQLSchema;
|
|
17
18
|
export declare const MAJIKAH_SQL_SCHEMA_MAJIK_CONTACTS: MajikahSQLSchema;
|
|
18
19
|
export declare const MAJIKAH_SQL_SCHEMA_MAJIK_CONTACT_GROUPS: MajikahSQLSchema;
|
|
20
|
+
export declare const MAJIKAH_SQL_SCHEMA_MAJIK_SIGNATURE_STAMPS: MajikahSQLSchema;
|
|
19
21
|
export declare const MAJIKAH_SQL_SCHEMA_FULL: MajikahSQLSchema;
|
|
20
22
|
export {};
|
|
@@ -8,6 +8,7 @@ export const MAJIKAH_SQL_TABLES = {
|
|
|
8
8
|
MAJIK_KEYS: "majik_keys",
|
|
9
9
|
MAJIK_CONTACTS: "majik_contacts",
|
|
10
10
|
MAJIK_CONTACT_GROUPS: "majik_contact_groups",
|
|
11
|
+
MAJIK_SIGNATURE_STAMPS: "majik_signature_stamps",
|
|
11
12
|
};
|
|
12
13
|
function normalizeSQL(sql) {
|
|
13
14
|
return sql
|
|
@@ -76,9 +77,35 @@ CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS} (
|
|
|
76
77
|
CREATE INDEX IF NOT EXISTS idx_majik_contact_groups_created_at
|
|
77
78
|
ON ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}(created_at);
|
|
78
79
|
`;
|
|
80
|
+
export const MAJIKAH_SQL_SCHEMA_MAJIK_SIGNATURE_STAMPS = `
|
|
81
|
+
CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS} (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
json TEXT NOT NULL,
|
|
84
|
+
kind TEXT NOT NULL,
|
|
85
|
+
name TEXT NOT NULL,
|
|
86
|
+
mime_type TEXT,
|
|
87
|
+
size_bytes INTEGER NOT NULL,
|
|
88
|
+
fingerprint TEXT NOT NULL,
|
|
89
|
+
created_at TEXT NOT NULL,
|
|
90
|
+
updated_at TEXT NOT NULL
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_majik_signature_stamps_kind
|
|
94
|
+
ON ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}(kind);
|
|
95
|
+
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_majik_signature_stamps_fingerprint
|
|
97
|
+
ON ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}(fingerprint);
|
|
98
|
+
|
|
99
|
+
CREATE INDEX IF NOT EXISTS idx_majik_signature_stamps_updated_at
|
|
100
|
+
ON ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}(updated_at);
|
|
101
|
+
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_majik_signature_stamps_fingerprint_kind
|
|
103
|
+
ON ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}(fingerprint, kind);
|
|
104
|
+
`;
|
|
79
105
|
export const MAJIKAH_SQL_SCHEMA_FULL = buildSchemaSQL([
|
|
80
106
|
MAJIKAH_SQL_SCHEMA_MAJIK_CLIENT_STATE,
|
|
81
107
|
MAJIKAH_SQL_SCHEMA_MAJIK_KEYS,
|
|
82
108
|
MAJIKAH_SQL_SCHEMA_MAJIK_CONTACTS,
|
|
83
109
|
MAJIKAH_SQL_SCHEMA_MAJIK_CONTACT_GROUPS,
|
|
110
|
+
MAJIKAH_SQL_SCHEMA_MAJIK_SIGNATURE_STAMPS,
|
|
84
111
|
]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MajikSignatureStampJSON } from "../../stamp/majik-signature-stamp";
|
|
2
|
+
import { MajikSignatureStampStorageAdapter } from "./_types";
|
|
3
|
+
export declare class InMemoryStampstoreAdapter implements MajikSignatureStampStorageAdapter {
|
|
4
|
+
private _store;
|
|
5
|
+
save(item: MajikSignatureStampJSON): Promise<void>;
|
|
6
|
+
getById(id: string): Promise<MajikSignatureStampJSON | null>;
|
|
7
|
+
list(): Promise<MajikSignatureStampJSON[]>;
|
|
8
|
+
remove(id: string): Promise<boolean>;
|
|
9
|
+
clear(): Promise<void>;
|
|
10
|
+
count(): Promise<number>;
|
|
11
|
+
exists(id: string): Promise<boolean>;
|
|
12
|
+
bulkSave(items: MajikSignatureStampJSON[]): Promise<void>;
|
|
13
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class InMemoryStampstoreAdapter {
|
|
2
|
+
_store = new Map();
|
|
3
|
+
async save(item) {
|
|
4
|
+
this._store.set(item.id, item);
|
|
5
|
+
}
|
|
6
|
+
async getById(id) {
|
|
7
|
+
return this._store.get(id) ?? null;
|
|
8
|
+
}
|
|
9
|
+
async list() {
|
|
10
|
+
return Array.from(this._store.values());
|
|
11
|
+
}
|
|
12
|
+
async remove(id) {
|
|
13
|
+
return this._store.delete(id);
|
|
14
|
+
}
|
|
15
|
+
async clear() {
|
|
16
|
+
this._store.clear();
|
|
17
|
+
}
|
|
18
|
+
async count() {
|
|
19
|
+
return this._store.size;
|
|
20
|
+
}
|
|
21
|
+
async exists(id) {
|
|
22
|
+
return this._store.has(id);
|
|
23
|
+
}
|
|
24
|
+
async bulkSave(items) {
|
|
25
|
+
for (const item of items)
|
|
26
|
+
this._store.set(item.id, item);
|
|
27
|
+
}
|
|
28
|
+
async bulkRemove(ids) {
|
|
29
|
+
for (const id of ids)
|
|
30
|
+
this._store.delete(id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MajikSignatureStampJSON } from "../../stamp/majik-signature-stamp";
|
|
2
|
+
import { MajikSignatureStampStorageAdapter } from "./_types";
|
|
3
|
+
import { SQLiteDatabase } from "../sql-db-manager";
|
|
4
|
+
import { StorageQuery } from "../storage-adapter";
|
|
5
|
+
export declare class SQLiteStampstoreAdapter implements MajikSignatureStampStorageAdapter {
|
|
6
|
+
private db;
|
|
7
|
+
constructor(db: SQLiteDatabase);
|
|
8
|
+
save(stamp: MajikSignatureStampJSON): Promise<void>;
|
|
9
|
+
getById(id: string): Promise<MajikSignatureStampJSON | null>;
|
|
10
|
+
list(): Promise<MajikSignatureStampJSON[]>;
|
|
11
|
+
remove(id: string): Promise<boolean>;
|
|
12
|
+
clear(): Promise<void>;
|
|
13
|
+
count(): Promise<number>;
|
|
14
|
+
exists(id: string): Promise<boolean>;
|
|
15
|
+
bulkSave(stamps: MajikSignatureStampJSON[]): Promise<void>;
|
|
16
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
17
|
+
query(query: StorageQuery<MajikSignatureStampJSON>): Promise<MajikSignatureStampJSON[]>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { MAJIKAH_SQL_TABLES } from "../sql-schema";
|
|
2
|
+
export class SQLiteStampstoreAdapter {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
async save(stamp) {
|
|
8
|
+
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}
|
|
9
|
+
(id, json, kind, name, mime_type, size_bytes, fingerprint, created_at, updated_at)
|
|
10
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
11
|
+
stamp.id,
|
|
12
|
+
JSON.stringify(stamp),
|
|
13
|
+
stamp.kind,
|
|
14
|
+
stamp.name,
|
|
15
|
+
stamp.mimeType,
|
|
16
|
+
stamp.sizeBytes,
|
|
17
|
+
stamp.fingerprint,
|
|
18
|
+
stamp.timestamp,
|
|
19
|
+
stamp.lastUpdate,
|
|
20
|
+
]);
|
|
21
|
+
}
|
|
22
|
+
async getById(id) {
|
|
23
|
+
const row = await this.db.get(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS} WHERE id = ?`, [id]);
|
|
24
|
+
return row ? JSON.parse(row.json) : null;
|
|
25
|
+
}
|
|
26
|
+
async list() {
|
|
27
|
+
const rows = await this.db.all(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}`);
|
|
28
|
+
return rows.map((r) => JSON.parse(r.json));
|
|
29
|
+
}
|
|
30
|
+
async remove(id) {
|
|
31
|
+
const exists = await this.exists(id);
|
|
32
|
+
if (!exists)
|
|
33
|
+
return false;
|
|
34
|
+
await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS} WHERE id = ?`, [id]);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
async clear() {
|
|
38
|
+
await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}`);
|
|
39
|
+
}
|
|
40
|
+
async count() {
|
|
41
|
+
const row = await this.db.get(`SELECT COUNT(*) as n FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}`);
|
|
42
|
+
return row?.n ?? 0;
|
|
43
|
+
}
|
|
44
|
+
async exists(id) {
|
|
45
|
+
const row = await this.db.get(`SELECT 1 FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS} WHERE id = ?`, [id]);
|
|
46
|
+
return !!row;
|
|
47
|
+
}
|
|
48
|
+
async bulkSave(stamps) {
|
|
49
|
+
if (stamps.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
await this.db.transaction(async (tx) => {
|
|
52
|
+
for (const s of stamps) {
|
|
53
|
+
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}
|
|
54
|
+
(id, json, kind, name, mime_type, size_bytes, fingerprint, created_at, updated_at)
|
|
55
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
56
|
+
s.id,
|
|
57
|
+
JSON.stringify(s),
|
|
58
|
+
s.kind,
|
|
59
|
+
s.name,
|
|
60
|
+
s.mimeType,
|
|
61
|
+
s.sizeBytes,
|
|
62
|
+
s.fingerprint,
|
|
63
|
+
s.timestamp,
|
|
64
|
+
s.lastUpdate,
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async bulkRemove(ids) {
|
|
70
|
+
if (ids.length === 0)
|
|
71
|
+
return;
|
|
72
|
+
await this.db.transaction(async (tx) => {
|
|
73
|
+
for (const id of ids) {
|
|
74
|
+
await tx.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS} WHERE id = ?`, [id]);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async query(query) {
|
|
79
|
+
const clauses = [];
|
|
80
|
+
const values = [];
|
|
81
|
+
if (query.where) {
|
|
82
|
+
for (const [key, value] of Object.entries(query.where)) {
|
|
83
|
+
clauses.push(`${key} = ?`);
|
|
84
|
+
values.push(value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let sql = `SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_SIGNATURE_STAMPS}`;
|
|
88
|
+
if (clauses.length > 0)
|
|
89
|
+
sql += ` WHERE ${clauses.join(" AND ")}`;
|
|
90
|
+
if (query.orderBy)
|
|
91
|
+
sql += ` ORDER BY ${String(query.orderBy)} ${query.orderDirection ?? "asc"}`;
|
|
92
|
+
if (query.limit)
|
|
93
|
+
sql += ` LIMIT ${query.limit}`;
|
|
94
|
+
if (query.offset)
|
|
95
|
+
sql += ` OFFSET ${query.offset}`;
|
|
96
|
+
const rows = await this.db.all(sql, values);
|
|
97
|
+
return rows.map((r) => JSON.parse(r.json));
|
|
98
|
+
}
|
|
99
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MajikContactManagerJSON } from "./contacts/types";
|
|
2
|
+
import { MajikSignatureStampJSON } from "./stamp/majik-signature-stamp";
|
|
2
3
|
import { UserAppPreferences } from "./storage";
|
|
3
4
|
export type ISODateString = string;
|
|
4
5
|
export type MajikMessageAccountID = string;
|
|
@@ -88,5 +89,6 @@ export interface MajikKeyMetadata {
|
|
|
88
89
|
}
|
|
89
90
|
export interface AppBackUpData {
|
|
90
91
|
contacts: MajikContactManagerJSON;
|
|
92
|
+
stamps?: MajikSignatureStampJSON[];
|
|
91
93
|
preferences?: UserAppPreferences;
|
|
92
94
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./core/contacts/majik-contact-groups";
|
|
|
6
6
|
export * from "./core/crypto/constants";
|
|
7
7
|
export * from "./core/crypto/keystore-manager";
|
|
8
8
|
export * from "./core/storage";
|
|
9
|
+
export * from "./core/stamp/majik-signature-stamp";
|
|
9
10
|
export * from "./core/utils/utilities";
|
|
10
11
|
export * from "./core/identity";
|
|
11
12
|
export { migrateMajikMessageJSON } from "./core/contacts/majik-contact-migration";
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./core/contacts/majik-contact-groups";
|
|
|
5
5
|
export * from "./core/crypto/constants";
|
|
6
6
|
export * from "./core/crypto/keystore-manager";
|
|
7
7
|
export * from "./core/storage";
|
|
8
|
+
export * from "./core/stamp/majik-signature-stamp";
|
|
8
9
|
export * from "./core/utils/utilities";
|
|
9
10
|
export * from "./core/identity";
|
|
10
11
|
export { migrateMajikMessageJSON } from "./core/contacts/majik-contact-migration";
|
|
@@ -23,10 +23,12 @@ import { MajikContactManager, MajikContactManagerAdapters } from "./core/contact
|
|
|
23
23
|
import { MajikContactManagerJSON } from "./core/contacts/types";
|
|
24
24
|
import { MajikContact, MajikContactGroup, MajikContactGroupMeta, MajikContactMeta, SerializedMajikContact } from "@majikah/majik-contact";
|
|
25
25
|
import { ClientStateManager } from "./core/client-state-manager";
|
|
26
|
-
import { ClientStateStorageAdapter, MajikKeyStorageAdapter, SQLiteDatabase, UserAppPreferences } from "./core/storage";
|
|
26
|
+
import { ClientStateStorageAdapter, MajikKeyStorageAdapter, MajikSignatureStampStorageAdapter, SQLiteDatabase, UserAppPreferences } from "./core/storage";
|
|
27
27
|
import { AppDataSnapshot, ContactManagerSnapshot } from "./core/backup/types";
|
|
28
28
|
import { MnemonicLanguage } from "@majikah/majik-key/dist/core/crypto/wordlist";
|
|
29
|
-
|
|
29
|
+
import { MajikSignatureStampManager } from "./core/stamp/majik-signature-stamp-manager";
|
|
30
|
+
import { MajikSignatureStamp, MajikSignatureStampJSON, StampAssetKind } from "./core/stamp/majik-signature-stamp";
|
|
31
|
+
type MajikSignatureClientEvents = "sign" | "verify" | "unlock" | "lock" | "error" | "new-stamp" | "removed-stamp" | "new-account" | "new-contact" | "new-contact-group" | "removed-account" | "removed-contact" | "removed-contact-group" | "contact-group-change" | "active-account-change" | "restore-backup";
|
|
30
32
|
type EventCallback = (...args: any[]) => void;
|
|
31
33
|
export interface MajikSignatureClientConfig {
|
|
32
34
|
dbSQL?: SQLiteDatabase;
|
|
@@ -46,9 +48,11 @@ export interface MajikSignatureClientConfig {
|
|
|
46
48
|
* is ignored.
|
|
47
49
|
*/
|
|
48
50
|
clientStateManager?: ClientStateManager;
|
|
51
|
+
stampsManager?: MajikSignatureStampManager;
|
|
49
52
|
adapters?: {
|
|
50
53
|
contacts?: MajikContactManagerAdapters;
|
|
51
54
|
keys?: MajikKeyStorageAdapter;
|
|
55
|
+
stamps?: MajikSignatureStampStorageAdapter;
|
|
52
56
|
/**
|
|
53
57
|
* Adapter for client-level state (account order, invoice defaults, etc.).
|
|
54
58
|
* Defaults to IDB_ADAPTER_CLIENT_STATE in browser environments.
|
|
@@ -79,6 +83,7 @@ export declare class MajikSignatureClient {
|
|
|
79
83
|
private _db;
|
|
80
84
|
private _contacts;
|
|
81
85
|
private _keys;
|
|
86
|
+
private _stamps;
|
|
82
87
|
private _state;
|
|
83
88
|
private _ownAccounts;
|
|
84
89
|
private _ownAccountsOrder;
|
|
@@ -89,6 +94,8 @@ export declare class MajikSignatureClient {
|
|
|
89
94
|
get keyManager(): MajikKeyManager;
|
|
90
95
|
/** Expose the client state manager for direct access if needed. */
|
|
91
96
|
get stateManager(): ClientStateManager;
|
|
97
|
+
/** Expose the stamp manager for direct access if needed. */
|
|
98
|
+
get stampManager(): MajikSignatureStampManager;
|
|
92
99
|
/**
|
|
93
100
|
* Load all domains from their adapters and restore client state.
|
|
94
101
|
* Call once on startup.
|
|
@@ -824,6 +831,19 @@ export declare class MajikSignatureClient {
|
|
|
824
831
|
isSealed(file: Blob, options?: {
|
|
825
832
|
mimeType?: string;
|
|
826
833
|
}): Promise<boolean>;
|
|
834
|
+
createStamp(data: Uint8Array | ArrayBuffer, kind: StampAssetKind, name: string, options?: {
|
|
835
|
+
mimeType?: string;
|
|
836
|
+
accountId?: string;
|
|
837
|
+
}): Promise<MajikSignatureStamp>;
|
|
838
|
+
listStamps(kind?: StampAssetKind): MajikSignatureStamp[];
|
|
839
|
+
getStamp(id: string): Promise<MajikSignatureStamp | null>;
|
|
840
|
+
decryptStampContent(id: string, accountId?: string): Promise<Uint8Array>;
|
|
841
|
+
renameStamp(id: string, newName: string): Promise<MajikSignatureStamp>;
|
|
842
|
+
replaceStampContent(id: string, data: Uint8Array | ArrayBuffer, options?: {
|
|
843
|
+
mimeType?: string;
|
|
844
|
+
accountId?: string;
|
|
845
|
+
}): Promise<MajikSignatureStamp>;
|
|
846
|
+
removeStamp(id: string): Promise<boolean>;
|
|
827
847
|
/**
|
|
828
848
|
* Sign an image with dual-layer embedding.
|
|
829
849
|
*
|
|
@@ -941,6 +961,12 @@ export declare class MajikSignatureClient {
|
|
|
941
961
|
* throughout MajikMessage — consistent account/contact resolution in one place.
|
|
942
962
|
*/
|
|
943
963
|
private _resolveSignerPublicKeys;
|
|
964
|
+
/**
|
|
965
|
+
* Resolve a MajikFileIdentity from an unlocked account, for stamp
|
|
966
|
+
* encryption/decryption. Requires the account to have ML-KEM keys and
|
|
967
|
+
* to already be unlocked — call ensureIdentityUnlocked() first if needed.
|
|
968
|
+
*/
|
|
969
|
+
private _resolveStampIdentity;
|
|
944
970
|
/**
|
|
945
971
|
* Wipe all data from every adapter and reset in-memory state.
|
|
946
972
|
* The client remains usable — call hydrate() or add new accounts after reset.
|
|
@@ -948,6 +974,9 @@ export declare class MajikSignatureClient {
|
|
|
948
974
|
resetData(): Promise<void>;
|
|
949
975
|
private _registerOwnAccount;
|
|
950
976
|
backupContacts(): Promise<Blob>;
|
|
977
|
+
backupStamps(): Promise<Blob>;
|
|
978
|
+
private _parseStampsBackup;
|
|
979
|
+
readStampsBackup(input: Blob | ArrayBufferLike | ArrayBufferView): Promise<MajikSignatureStampJSON[]>;
|
|
951
980
|
backupAppData(): Promise<Blob>;
|
|
952
981
|
/**
|
|
953
982
|
* Parses a contacts backup blob into a ContactManagerSnapshot —
|
|
@@ -955,6 +984,9 @@ export declare class MajikSignatureClient {
|
|
|
955
984
|
* No side-effects; nothing is written to the live store.
|
|
956
985
|
*/
|
|
957
986
|
private _parseContactsBackup;
|
|
987
|
+
restoreStamps(input: Blob | ArrayBufferLike | ArrayBufferView): Promise<{
|
|
988
|
+
restored: number;
|
|
989
|
+
}>;
|
|
958
990
|
/**
|
|
959
991
|
* Restores contacts (and optionally groups) from a contacts backup blob.
|
|
960
992
|
*
|
|
@@ -979,7 +1011,7 @@ export declare class MajikSignatureClient {
|
|
|
979
1011
|
readContactsBackup(input: Blob | ArrayBufferLike | ArrayBufferView): Promise<ContactManagerSnapshot>;
|
|
980
1012
|
/**
|
|
981
1013
|
* Restores all data from a full backup blob produced by `backupAppData()`.
|
|
982
|
-
* Contacts and groups are restored before
|
|
1014
|
+
* Contacts and groups are restored before stamps so recipients resolve
|
|
983
1015
|
* correctly.
|
|
984
1016
|
*/
|
|
985
1017
|
restoreAppData(blob: Blob): Promise<{
|
|
@@ -990,9 +1022,9 @@ export declare class MajikSignatureClient {
|
|
|
990
1022
|
* Probes the first bytes of a blob and returns which backup type it is,
|
|
991
1023
|
* without fully parsing it. Useful for file-picker validation UI.
|
|
992
1024
|
*
|
|
993
|
-
* @returns `"
|
|
1025
|
+
* @returns `"stamps" | "contacts" | "appData" | "unknown"`
|
|
994
1026
|
*/
|
|
995
|
-
static probeBackupType(blob: Blob): Promise<"
|
|
1027
|
+
static probeBackupType(blob: Blob): Promise<"stamps" | "contacts" | "appData" | "unknown">;
|
|
996
1028
|
/**
|
|
997
1029
|
* Parses an app data backup blob into an AppDataSnapshot.
|
|
998
1030
|
* No side-effects; nothing is written to the live store.
|
|
@@ -1008,6 +1040,7 @@ export declare class MajikSignatureClient {
|
|
|
1008
1040
|
* The caller controls exactly which domains are written.
|
|
1009
1041
|
*/
|
|
1010
1042
|
restoreAppDataSelective(snapshot: AppDataSnapshot, options?: {
|
|
1043
|
+
stamps?: boolean;
|
|
1011
1044
|
contacts?: boolean;
|
|
1012
1045
|
groups?: boolean;
|
|
1013
1046
|
preferences?: boolean;
|
|
@@ -19,15 +19,18 @@ import { MajikSignature } from "@majikah/majik-signature";
|
|
|
19
19
|
import { base64ToUint8Array } from "./core/utils/utilities";
|
|
20
20
|
import { MajikContactManager, } from "./core/contacts/majik-contact-manager";
|
|
21
21
|
import { ClientStateManager } from "./core/client-state-manager";
|
|
22
|
-
import { InMemoryClientStateAdapter, InMemoryKeystoreAdapter, } from "./core/storage";
|
|
22
|
+
import { InMemoryClientStateAdapter, InMemoryKeystoreAdapter, InMemoryStampstoreAdapter, } from "./core/storage";
|
|
23
23
|
import { MajikCompressedJSON } from "@majikah/majik-cjson";
|
|
24
24
|
import { prependMagic, readBackupBlob } from "./core/backup/utils";
|
|
25
|
-
import {
|
|
25
|
+
import { MAJIK_SIGNATURE_BACKUP_MAGIC, MAJIK_MESSAGE_BACKUP_MAGIC_SIZE, } from "./core/backup/constants";
|
|
26
|
+
import { MajikSignatureStampManager } from "./core/stamp/majik-signature-stamp-manager";
|
|
27
|
+
import { MajikSignatureStamp, } from "./core/stamp/majik-signature-stamp";
|
|
26
28
|
// ─── MajikSignatureClient ─────────────────────────────────────────────────────
|
|
27
29
|
export class MajikSignatureClient {
|
|
28
30
|
_db;
|
|
29
31
|
_contacts;
|
|
30
32
|
_keys;
|
|
33
|
+
_stamps;
|
|
31
34
|
_state;
|
|
32
35
|
_ownAccounts = new Map();
|
|
33
36
|
_ownAccountsOrder = [];
|
|
@@ -41,8 +44,9 @@ export class MajikSignatureClient {
|
|
|
41
44
|
this._keys =
|
|
42
45
|
config.keyManager ??
|
|
43
46
|
new MajikKeyManager(config.adapters?.keys ?? new InMemoryKeystoreAdapter());
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
this._stamps =
|
|
48
|
+
config.stampsManager ??
|
|
49
|
+
new MajikSignatureStampManager(config.adapters?.stamps ?? new InMemoryStampstoreAdapter());
|
|
46
50
|
this._state =
|
|
47
51
|
config.clientStateManager ??
|
|
48
52
|
new ClientStateManager(config.adapters?.clientState ?? new InMemoryClientStateAdapter());
|
|
@@ -52,6 +56,8 @@ export class MajikSignatureClient {
|
|
|
52
56
|
"unlock",
|
|
53
57
|
"lock",
|
|
54
58
|
"error",
|
|
59
|
+
"new-stamp",
|
|
60
|
+
"removed-stamp",
|
|
55
61
|
"new-account",
|
|
56
62
|
"new-contact",
|
|
57
63
|
"new-contact-group",
|
|
@@ -72,6 +78,10 @@ export class MajikSignatureClient {
|
|
|
72
78
|
get stateManager() {
|
|
73
79
|
return this._state;
|
|
74
80
|
}
|
|
81
|
+
/** Expose the stamp manager for direct access if needed. */
|
|
82
|
+
get stampManager() {
|
|
83
|
+
return this._stamps;
|
|
84
|
+
}
|
|
75
85
|
// ── Hydration ─────────────────────────────────────────────────────────────
|
|
76
86
|
/**
|
|
77
87
|
* Load all domains from their adapters and restore client state.
|
|
@@ -87,6 +97,7 @@ export class MajikSignatureClient {
|
|
|
87
97
|
await this._keys.hydrate();
|
|
88
98
|
// 2. Contacts + groups
|
|
89
99
|
await this._contacts.hydrate();
|
|
100
|
+
await this._stamps.hydrate();
|
|
90
101
|
// 4. Client state — account order, app settings, etc.
|
|
91
102
|
await this._state.hydrate();
|
|
92
103
|
// 5. Own accounts — rebuild from keys loaded in step 1
|
|
@@ -1642,6 +1653,51 @@ export class MajikSignatureClient {
|
|
|
1642
1653
|
throw err;
|
|
1643
1654
|
}
|
|
1644
1655
|
}
|
|
1656
|
+
// ── Stamps (encrypted reusable assets — signatures, audio, video, text) ────
|
|
1657
|
+
async createStamp(data, kind, name, options) {
|
|
1658
|
+
try {
|
|
1659
|
+
const identity = this._resolveStampIdentity(options?.accountId);
|
|
1660
|
+
const stamp = await this._stamps.create({
|
|
1661
|
+
data,
|
|
1662
|
+
identity,
|
|
1663
|
+
kind,
|
|
1664
|
+
name,
|
|
1665
|
+
mimeType: options?.mimeType,
|
|
1666
|
+
});
|
|
1667
|
+
this._emit("new-stamp", stamp);
|
|
1668
|
+
return stamp;
|
|
1669
|
+
}
|
|
1670
|
+
catch (err) {
|
|
1671
|
+
this._emit("error", err, { context: "createStamp" });
|
|
1672
|
+
throw err;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
listStamps(kind) {
|
|
1676
|
+
return kind ? this._stamps.listByKind(kind) : this._stamps.list();
|
|
1677
|
+
}
|
|
1678
|
+
async getStamp(id) {
|
|
1679
|
+
return this._stamps.load(id);
|
|
1680
|
+
}
|
|
1681
|
+
async decryptStampContent(id, accountId) {
|
|
1682
|
+
const identity = this._resolveStampIdentity(accountId);
|
|
1683
|
+
return this._stamps.decryptContent(id, identity);
|
|
1684
|
+
}
|
|
1685
|
+
async renameStamp(id, newName) {
|
|
1686
|
+
return this._stamps.rename(id, newName);
|
|
1687
|
+
}
|
|
1688
|
+
async replaceStampContent(id, data, options) {
|
|
1689
|
+
const identity = this._resolveStampIdentity(options?.accountId);
|
|
1690
|
+
const raw = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
1691
|
+
return this._stamps.replaceContent(id, raw, identity, options?.mimeType);
|
|
1692
|
+
}
|
|
1693
|
+
async removeStamp(id) {
|
|
1694
|
+
const existed = await this._stamps.has(id);
|
|
1695
|
+
if (!existed)
|
|
1696
|
+
return false;
|
|
1697
|
+
await this._stamps.delete(id);
|
|
1698
|
+
this._emit("removed-stamp", id);
|
|
1699
|
+
return true;
|
|
1700
|
+
}
|
|
1645
1701
|
// ── STAMP (compression-resistant image signing) ───────────────────────────
|
|
1646
1702
|
//
|
|
1647
1703
|
// These methods delegate to MajikImageSignature, passing `MajikSignature`
|
|
@@ -1815,6 +1871,33 @@ export class MajikSignatureClient {
|
|
|
1815
1871
|
}
|
|
1816
1872
|
return null;
|
|
1817
1873
|
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Resolve a MajikFileIdentity from an unlocked account, for stamp
|
|
1876
|
+
* encryption/decryption. Requires the account to have ML-KEM keys and
|
|
1877
|
+
* to already be unlocked — call ensureIdentityUnlocked() first if needed.
|
|
1878
|
+
*/
|
|
1879
|
+
_resolveStampIdentity(accountId) {
|
|
1880
|
+
const id = accountId ?? this.getActiveAccount()?.id;
|
|
1881
|
+
if (!id)
|
|
1882
|
+
throw new Error("No active account — call setActiveAccount() first");
|
|
1883
|
+
const key = this._keys.get(id);
|
|
1884
|
+
if (!key)
|
|
1885
|
+
throw new Error(`Account not found in keystore: "${id}"`);
|
|
1886
|
+
if (key.isLocked) {
|
|
1887
|
+
throw new Error(`Account "${id}" is locked. Call unlockAccount() before using stamps.`);
|
|
1888
|
+
}
|
|
1889
|
+
const mlKemSecretKey = this._keys.getMlKemSecretKey(id);
|
|
1890
|
+
if (!mlKemSecretKey) {
|
|
1891
|
+
throw new Error(`Account "${id}" has no ML-KEM keys. Re-import via ` +
|
|
1892
|
+
`importAccountFromMnemonicBackup() to enable stamp encryption.`);
|
|
1893
|
+
}
|
|
1894
|
+
return {
|
|
1895
|
+
publicKey: key.publicKeyBase64,
|
|
1896
|
+
fingerprint: key.fingerprint,
|
|
1897
|
+
mlKemPublicKey: key.mlKemPublicKey,
|
|
1898
|
+
mlKemSecretKey,
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1818
1901
|
// ==========================================================================
|
|
1819
1902
|
// ── RESET ─────────────────────────────────────────────────────────────────
|
|
1820
1903
|
// ==========================================================================
|
|
@@ -1826,6 +1909,7 @@ export class MajikSignatureClient {
|
|
|
1826
1909
|
try {
|
|
1827
1910
|
await this._keys.adapter.clear();
|
|
1828
1911
|
await this._contacts.clear();
|
|
1912
|
+
await this._stamps.adapter.clear(); // NEW
|
|
1829
1913
|
await this._state.clear();
|
|
1830
1914
|
if (this._db) {
|
|
1831
1915
|
await this._db.vacuum();
|
|
@@ -1834,6 +1918,7 @@ export class MajikSignatureClient {
|
|
|
1834
1918
|
this._ownAccounts.clear();
|
|
1835
1919
|
this._ownAccountsOrder = [];
|
|
1836
1920
|
this._keys = new MajikKeyManager(this._keys.adapter);
|
|
1921
|
+
this._stamps = new MajikSignatureStampManager(this._stamps.adapter); // NEW
|
|
1837
1922
|
this._emit("active-account-change", null);
|
|
1838
1923
|
}
|
|
1839
1924
|
catch (err) {
|
|
@@ -1864,21 +1949,40 @@ export class MajikSignatureClient {
|
|
|
1864
1949
|
const managerJSON = await this._contacts.toJSON();
|
|
1865
1950
|
const cj = MajikCompressedJSON.create(managerJSON);
|
|
1866
1951
|
const payload = cj.toBinary();
|
|
1867
|
-
const stamped = prependMagic(
|
|
1952
|
+
const stamped = prependMagic(MAJIK_SIGNATURE_BACKUP_MAGIC.contacts, payload);
|
|
1953
|
+
return new Blob([stamped], {
|
|
1954
|
+
type: "application/octet-stream",
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
async backupStamps() {
|
|
1958
|
+
const stampsJSON = await this._stamps.toJSON();
|
|
1959
|
+
const cj = MajikCompressedJSON.create(stampsJSON);
|
|
1960
|
+
const payload = cj.toBinary();
|
|
1961
|
+
const stamped = prependMagic(MAJIK_SIGNATURE_BACKUP_MAGIC.stamps, payload);
|
|
1868
1962
|
return new Blob([stamped], {
|
|
1869
1963
|
type: "application/octet-stream",
|
|
1870
1964
|
});
|
|
1871
1965
|
}
|
|
1966
|
+
async _parseStampsBackup(input) {
|
|
1967
|
+
const payload = await readBackupBlob(input, MAJIK_SIGNATURE_BACKUP_MAGIC.stamps, "stamps");
|
|
1968
|
+
const cj = await MajikCompressedJSON.fromMJKCJSON(payload);
|
|
1969
|
+
return cj.payload;
|
|
1970
|
+
}
|
|
1971
|
+
async readStampsBackup(input) {
|
|
1972
|
+
return this._parseStampsBackup(input);
|
|
1973
|
+
}
|
|
1872
1974
|
async backupAppData() {
|
|
1873
1975
|
const contactsJSON = await this._contacts.toJSON();
|
|
1976
|
+
const stampsJSON = await this._stamps.toJSON(); // NEW
|
|
1874
1977
|
const userPref = await this.getUserAppPreferences();
|
|
1875
1978
|
const backupJSON = {
|
|
1876
1979
|
contacts: contactsJSON,
|
|
1980
|
+
stamps: stampsJSON, // NEW
|
|
1877
1981
|
preferences: userPref ?? undefined,
|
|
1878
1982
|
};
|
|
1879
1983
|
const cj = MajikCompressedJSON.create(backupJSON);
|
|
1880
1984
|
const payload = cj.toBinary();
|
|
1881
|
-
const stamped = prependMagic(
|
|
1985
|
+
const stamped = prependMagic(MAJIK_SIGNATURE_BACKUP_MAGIC.appData, payload);
|
|
1882
1986
|
return new Blob([stamped], {
|
|
1883
1987
|
type: "application/octet-stream",
|
|
1884
1988
|
});
|
|
@@ -1893,7 +1997,7 @@ export class MajikSignatureClient {
|
|
|
1893
1997
|
* No side-effects; nothing is written to the live store.
|
|
1894
1998
|
*/
|
|
1895
1999
|
async _parseContactsBackup(input) {
|
|
1896
|
-
const payload = await readBackupBlob(input,
|
|
2000
|
+
const payload = await readBackupBlob(input, MAJIK_SIGNATURE_BACKUP_MAGIC.contacts, "contacts");
|
|
1897
2001
|
const cj = await MajikCompressedJSON.fromMJKCJSON(payload);
|
|
1898
2002
|
const managerJSON = cj.payload;
|
|
1899
2003
|
// Hydrate a throw-away manager so callers get real instances, not raw JSON
|
|
@@ -1904,13 +2008,11 @@ export class MajikSignatureClient {
|
|
|
1904
2008
|
return { managerJSON, contacts, groups };
|
|
1905
2009
|
}
|
|
1906
2010
|
// ── Public restore (saves to store) ───────────────────────────────────────
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
// return { restored: chatMessages.length };
|
|
1913
|
-
// }
|
|
2011
|
+
async restoreStamps(input) {
|
|
2012
|
+
const stampsJSON = await this._parseStampsBackup(input);
|
|
2013
|
+
await Promise.all(stampsJSON.map((json) => this._stamps.save(MajikSignatureStamp.fromJSON(json))));
|
|
2014
|
+
return { restored: stampsJSON.length };
|
|
2015
|
+
}
|
|
1914
2016
|
/**
|
|
1915
2017
|
* Restores contacts (and optionally groups) from a contacts backup blob.
|
|
1916
2018
|
*
|
|
@@ -1956,11 +2058,6 @@ export class MajikSignatureClient {
|
|
|
1956
2058
|
return { contacts: contactCount, groups: groupCount };
|
|
1957
2059
|
}
|
|
1958
2060
|
// ── Public read (no side-effects) ─────────────────────────────────────────
|
|
1959
|
-
// async readInvoicesBackup(
|
|
1960
|
-
// input: Blob | ArrayBufferLike | ArrayBufferView,
|
|
1961
|
-
// ): Promise<MajikInvoice[]> {
|
|
1962
|
-
// return this._parseInvoicesBackup(input);
|
|
1963
|
-
// }
|
|
1964
2061
|
/**
|
|
1965
2062
|
* Parses a contacts backup without writing anything to the live store.
|
|
1966
2063
|
* Returns contacts and user-defined groups for the caller to preview.
|
|
@@ -1970,11 +2067,11 @@ export class MajikSignatureClient {
|
|
|
1970
2067
|
}
|
|
1971
2068
|
/**
|
|
1972
2069
|
* Restores all data from a full backup blob produced by `backupAppData()`.
|
|
1973
|
-
* Contacts and groups are restored before
|
|
2070
|
+
* Contacts and groups are restored before stamps so recipients resolve
|
|
1974
2071
|
* correctly.
|
|
1975
2072
|
*/
|
|
1976
2073
|
async restoreAppData(blob) {
|
|
1977
|
-
const payload = await readBackupBlob(blob,
|
|
2074
|
+
const payload = await readBackupBlob(blob, MAJIK_SIGNATURE_BACKUP_MAGIC.appData, "app data");
|
|
1978
2075
|
const cj = await MajikCompressedJSON.fromMJKCJSON(payload);
|
|
1979
2076
|
const data = cj.payload;
|
|
1980
2077
|
// 1. Contacts
|
|
@@ -1998,13 +2095,12 @@ export class MajikSignatureClient {
|
|
|
1998
2095
|
}
|
|
1999
2096
|
}
|
|
2000
2097
|
}
|
|
2001
|
-
// 2.
|
|
2002
|
-
//
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
// );
|
|
2098
|
+
// 2. Stamps
|
|
2099
|
+
// inside restoreAppData(), after the contacts/groups block:
|
|
2100
|
+
await Promise.all((data.stamps ?? []).map((json) => {
|
|
2101
|
+
const stamp = MajikSignatureStamp.fromJSON(json);
|
|
2102
|
+
return this._stamps.save(stamp);
|
|
2103
|
+
}));
|
|
2008
2104
|
if (data.preferences) {
|
|
2009
2105
|
await this.setUserAppPreferences(data.preferences);
|
|
2010
2106
|
}
|
|
@@ -2017,11 +2113,11 @@ export class MajikSignatureClient {
|
|
|
2017
2113
|
* Probes the first bytes of a blob and returns which backup type it is,
|
|
2018
2114
|
* without fully parsing it. Useful for file-picker validation UI.
|
|
2019
2115
|
*
|
|
2020
|
-
* @returns `"
|
|
2116
|
+
* @returns `"stamps" | "contacts" | "appData" | "unknown"`
|
|
2021
2117
|
*/
|
|
2022
2118
|
static async probeBackupType(blob) {
|
|
2023
2119
|
const header = new Uint8Array(await blob.slice(0, MAJIK_MESSAGE_BACKUP_MAGIC_SIZE).arrayBuffer());
|
|
2024
|
-
for (const [type, magic] of Object.entries(
|
|
2120
|
+
for (const [type, magic] of Object.entries(MAJIK_SIGNATURE_BACKUP_MAGIC)) {
|
|
2025
2121
|
if (magic.every((byte, i) => header[i] === byte))
|
|
2026
2122
|
return type;
|
|
2027
2123
|
}
|
|
@@ -2033,7 +2129,7 @@ export class MajikSignatureClient {
|
|
|
2033
2129
|
* No side-effects; nothing is written to the live store.
|
|
2034
2130
|
*/
|
|
2035
2131
|
async _parseAppDataBackup(input) {
|
|
2036
|
-
const payload = await readBackupBlob(input,
|
|
2132
|
+
const payload = await readBackupBlob(input, MAJIK_SIGNATURE_BACKUP_MAGIC.appData, "app data");
|
|
2037
2133
|
const cj = await MajikCompressedJSON.fromMJKCJSON(payload);
|
|
2038
2134
|
const data = cj.payload;
|
|
2039
2135
|
const tempManager = await MajikContactManager.fromJSON(data.contacts);
|
|
@@ -2042,8 +2138,8 @@ export class MajikSignatureClient {
|
|
|
2042
2138
|
return {
|
|
2043
2139
|
contacts,
|
|
2044
2140
|
groups,
|
|
2141
|
+
stamps: data.stamps ?? [], // NEW
|
|
2045
2142
|
preferences: data.preferences ?? null,
|
|
2046
|
-
// Keep raw manager JSON for the restore path
|
|
2047
2143
|
_contactsManagerJSON: data.contacts,
|
|
2048
2144
|
};
|
|
2049
2145
|
}
|
|
@@ -2061,15 +2157,13 @@ export class MajikSignatureClient {
|
|
|
2061
2157
|
* The caller controls exactly which domains are written.
|
|
2062
2158
|
*/
|
|
2063
2159
|
async restoreAppDataSelective(snapshot, options = {}) {
|
|
2064
|
-
const {
|
|
2065
|
-
|
|
2066
|
-
contacts: doContacts = true, groups: doGroups = true, preferences: doPreferences = true, overwriteContacts = true, } = options;
|
|
2067
|
-
let invoiceCount = 0;
|
|
2160
|
+
const { stamps: doStamps = true, contacts: doContacts = true, groups: doGroups = true, preferences: doPreferences = true, overwriteContacts = true, } = options;
|
|
2161
|
+
let stampCount = 0;
|
|
2068
2162
|
let contactCount = 0;
|
|
2069
2163
|
let groupCount = 0;
|
|
2070
2164
|
let defaultsRestored = false;
|
|
2071
2165
|
let preferencesRestored = false;
|
|
2072
|
-
// 1. Contacts first
|
|
2166
|
+
// 1. Contacts first
|
|
2073
2167
|
if (doContacts) {
|
|
2074
2168
|
for (const contact of snapshot.contacts) {
|
|
2075
2169
|
const exists = !!this._contacts.getContact(contact.id);
|
|
@@ -2097,20 +2191,18 @@ export class MajikSignatureClient {
|
|
|
2097
2191
|
groupCount++;
|
|
2098
2192
|
}
|
|
2099
2193
|
}
|
|
2100
|
-
// 3.
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
// invoiceCount = snapshot.chatMessages.length;
|
|
2106
|
-
// }
|
|
2194
|
+
// 3. Stamps
|
|
2195
|
+
if (doStamps) {
|
|
2196
|
+
await Promise.all(snapshot.stamps.map((json) => this._stamps.save(MajikSignatureStamp.fromJSON(json))));
|
|
2197
|
+
stampCount = snapshot.stamps.length;
|
|
2198
|
+
}
|
|
2107
2199
|
// 5. App preferences
|
|
2108
2200
|
if (doPreferences && snapshot.preferences) {
|
|
2109
2201
|
await this.setUserAppPreferences(snapshot.preferences);
|
|
2110
2202
|
preferencesRestored = true;
|
|
2111
2203
|
}
|
|
2112
2204
|
const restoredData = {
|
|
2113
|
-
|
|
2205
|
+
stamps: stampCount,
|
|
2114
2206
|
contacts: contactCount,
|
|
2115
2207
|
groups: groupCount,
|
|
2116
2208
|
invoiceDefaults: defaultsRestored,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@majikah/majik-signature-client",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Majik Signature Client is a hybrid post-quantum content signing and verification library for the Majikah ecosystem. Built on top of Majik Key, it provides tamper-proof, forgery-resistant digital signatures for any content format — using a dual-algorithm architecture that combines classical Ed25519 with post-quantum ML-DSA-87 (FIPS-204).",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.0",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@majikah/majik-cjson": "^0.0.3",
|
|
56
56
|
"@majikah/majik-contact": "^0.0.6",
|
|
57
|
+
"@majikah/majik-file": "^0.1.7",
|
|
57
58
|
"@majikah/majik-key": "^0.2.12",
|
|
58
59
|
"@majikah/majik-signature": "^0.0.30",
|
|
59
60
|
"@stablelib/ed25519": "^2.1.0",
|