@majikah/majik-signature-client 0.2.0 → 0.3.1

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.
@@ -6,14 +6,14 @@
6
6
  * MJKC = MajiK Contacts
7
7
  * MJKA = MajiK App-data
8
8
  */
9
- export declare const MAJIK_MESSAGE_BACKUP_MAGIC: {
10
- readonly chats: Uint8Array;
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 MAJIK_MESSAGE_BACKUP_MAGIC)[keyof typeof MAJIK_MESSAGE_BACKUP_MAGIC];
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 MAJIK_MESSAGE_BACKUP_MAGIC;
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 MAJIK_MESSAGE_BACKUP_MAGIC = {
10
- chats: new Uint8Array([0x4d, 0x4a, 0x4b, 0x49, 0x00, 0x01]), // "MJKI" v1
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
  }
@@ -226,4 +226,9 @@ export const DEFAULT_USER_APP_PREFERENCES = {
226
226
  privacy: {
227
227
  shareAnalytics: true,
228
228
  },
229
+ security: {
230
+ key: {
231
+ autoLockOnMinimize: false,
232
+ },
233
+ },
229
234
  };
@@ -0,0 +1,57 @@
1
+ import { type BatchLockResult, 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
+ import { MajikKey } from "@majikah/majik-key";
6
+ export declare class MajikSignatureStampManagerError extends Error {
7
+ cause?: unknown;
8
+ constructor(message: string, cause?: unknown);
9
+ }
10
+ export declare class MajikSignatureStampManager {
11
+ private readonly _cache;
12
+ private _decryptErrors;
13
+ private _adapter;
14
+ constructor(adapter?: MajikSignatureStampStorageAdapter);
15
+ get adapter(): MajikSignatureStampStorageAdapter;
16
+ setAdapter(adapter: MajikSignatureStampStorageAdapter): void;
17
+ hydrate(): Promise<void>;
18
+ /**
19
+ * Hydrate every stamp for `fingerprint`. If `key` is given (must be
20
+ * unlocked, matching that fingerprint), each stamp is decrypted so its
21
+ * plaintext is ready for preview. Stamps that fail to decrypt are
22
+ * silently excluded from the cache — never a hard throw. Reasons are
23
+ * kept in getDecryptError() for optional diagnostics.
24
+ *
25
+ * NOTE: this intentionally does NOT call MajikFile.batchDecrypt() —
26
+ * that method gates on MajikFile.canDecrypt(), which currently compares
27
+ * key.fingerprint against a list of public keys and will reject every
28
+ * file. Once that's fixed upstream, this can call batchDecrypt() directly
29
+ * instead of looping decryptHydrate() manually.
30
+ */
31
+ hydrateForFingerprint(fingerprint: string, key?: MajikKey): Promise<void>;
32
+ getDecryptError(id: string): string | undefined;
33
+ /** Wipe decrypted plaintext from every currently cached stamp's MajikFile.
34
+ * MajikFile.batchLock() has no canDecrypt-style gate, safe to use as-is. */
35
+ lockAll(): BatchLockResult;
36
+ toJSON(): Promise<MajikSignatureStampJSON[]>;
37
+ bulkRestoreFromJSON(data: MajikSignatureStampJSON[]): Promise<void>;
38
+ static fromJSON(data: MajikSignatureStampJSON[], adapter?: MajikSignatureStampStorageAdapter): Promise<MajikSignatureStampManager>;
39
+ private _persist;
40
+ create(options: CreateStampOptions): Promise<MajikSignatureStamp>;
41
+ save(stamp: MajikSignatureStamp): Promise<void>;
42
+ load(id: string): Promise<MajikSignatureStamp | null>;
43
+ getStampOrThrow(id: string): Promise<MajikSignatureStamp>;
44
+ loadAll(): Promise<MajikSignatureStamp[]>;
45
+ delete(id: string): Promise<void>;
46
+ has(id: string): Promise<boolean>;
47
+ get(id: string): MajikSignatureStamp | undefined;
48
+ list(): MajikSignatureStamp[];
49
+ listByKind(kind: StampAssetKind): MajikSignatureStamp[];
50
+ listByFingerprint(fingerprint: string): MajikSignatureStamp[];
51
+ query(query: StorageQuery<MajikSignatureStampJSON>): Promise<MajikSignatureStampJSON[]>;
52
+ rename(id: string, newName: string): Promise<MajikSignatureStamp>;
53
+ /** Write-once at the MajikFile layer — id is preserved explicitly. */
54
+ replaceContent(id: string, rawBytes: Uint8Array, identity: MajikFileIdentity, mimeType?: string): Promise<MajikSignatureStamp>;
55
+ decryptContent(id: string, identity: Pick<MajikFileIdentity, "fingerprint" | "mlKemSecretKey">): Promise<Uint8Array>;
56
+ bulkRemove(ids: string[]): Promise<void>;
57
+ }
@@ -0,0 +1,216 @@
1
+ import { MajikFile, } from "@majikah/majik-file";
2
+ import { MajikSignatureStamp, } from "./majik-signature-stamp";
3
+ import { InMemoryStampstoreAdapter, } from "../storage";
4
+ export class MajikSignatureStampManagerError extends Error {
5
+ cause;
6
+ constructor(message, cause) {
7
+ super(message);
8
+ this.name = "MajikSignatureStampManagerError";
9
+ this.cause = cause;
10
+ }
11
+ }
12
+ export class MajikSignatureStampManager {
13
+ _cache = new Map();
14
+ _decryptErrors = new Map();
15
+ _adapter;
16
+ constructor(adapter) {
17
+ this._adapter = adapter ?? new InMemoryStampstoreAdapter();
18
+ }
19
+ get adapter() {
20
+ return this._adapter;
21
+ }
22
+ setAdapter(adapter) {
23
+ this._adapter = adapter;
24
+ }
25
+ // ── Hydration ─────────────────────────────────────────────────────────
26
+ async hydrate() {
27
+ this._cache.clear();
28
+ const all = await this._adapter.list();
29
+ for (const json of all) {
30
+ try {
31
+ const stamp = MajikSignatureStamp.fromJSON(json);
32
+ this._cache.set(stamp.id, stamp);
33
+ }
34
+ catch (err) {
35
+ console.warn(`MajikSignatureStampManager.hydrate: skipping malformed stamp "${json?.id}":`, err);
36
+ }
37
+ }
38
+ }
39
+ /**
40
+ * Hydrate every stamp for `fingerprint`. If `key` is given (must be
41
+ * unlocked, matching that fingerprint), each stamp is decrypted so its
42
+ * plaintext is ready for preview. Stamps that fail to decrypt are
43
+ * silently excluded from the cache — never a hard throw. Reasons are
44
+ * kept in getDecryptError() for optional diagnostics.
45
+ *
46
+ * NOTE: this intentionally does NOT call MajikFile.batchDecrypt() —
47
+ * that method gates on MajikFile.canDecrypt(), which currently compares
48
+ * key.fingerprint against a list of public keys and will reject every
49
+ * file. Once that's fixed upstream, this can call batchDecrypt() directly
50
+ * instead of looping decryptHydrate() manually.
51
+ */
52
+ async hydrateForFingerprint(fingerprint, key) {
53
+ this._cache.clear();
54
+ this._decryptErrors.clear();
55
+ const rows = this._adapter.query
56
+ ? await this._adapter.query({ where: { fingerprint } })
57
+ : (await this._adapter.list()).filter((r) => r.fingerprint === fingerprint);
58
+ const stamps = [];
59
+ for (const json of rows) {
60
+ try {
61
+ stamps.push(MajikSignatureStamp.fromJSON(json));
62
+ }
63
+ catch (err) {
64
+ console.warn(`hydrateForFingerprint: skipping malformed stamp "${json?.id}":`, err);
65
+ }
66
+ }
67
+ if (!key) {
68
+ for (const s of stamps)
69
+ this._cache.set(s.id, s);
70
+ return;
71
+ }
72
+ const results = await Promise.allSettled(stamps.map((stamp) => stamp.toMajikFile().decryptHydrate(key)));
73
+ results.forEach((result, i) => {
74
+ const stamp = stamps[i];
75
+ if (result.status === "fulfilled") {
76
+ this._cache.set(stamp.id, stamp);
77
+ }
78
+ else {
79
+ this._decryptErrors.set(stamp.id, result.reason instanceof Error
80
+ ? result.reason.message
81
+ : String(result.reason));
82
+ }
83
+ });
84
+ }
85
+ getDecryptError(id) {
86
+ return this._decryptErrors.get(id);
87
+ }
88
+ /** Wipe decrypted plaintext from every currently cached stamp's MajikFile.
89
+ * MajikFile.batchLock() has no canDecrypt-style gate, safe to use as-is. */
90
+ lockAll() {
91
+ const files = this.list().map((s) => s.toMajikFile());
92
+ return MajikFile.batchLock(files);
93
+ }
94
+ // ── Serialization ─────────────────────────────────────────────────────
95
+ async toJSON() {
96
+ return this._adapter.list();
97
+ }
98
+ async bulkRestoreFromJSON(data) {
99
+ if (!Array.isArray(data)) {
100
+ throw new MajikSignatureStampManagerError("bulkRestoreFromJSON: expected MajikSignatureStampJSON[]");
101
+ }
102
+ await this._adapter.bulkSave(data);
103
+ await this.hydrate();
104
+ }
105
+ static async fromJSON(data, adapter) {
106
+ const manager = new MajikSignatureStampManager(adapter);
107
+ await manager.bulkRestoreFromJSON(data);
108
+ return manager;
109
+ }
110
+ async _persist(stamp) {
111
+ await this._adapter.save(stamp.toJSON());
112
+ }
113
+ // ── Core CRUD ───────────────────────────────────────────────────────────
114
+ async create(options) {
115
+ const stamp = await MajikSignatureStamp.create(options);
116
+ await this._persist(stamp);
117
+ this._cache.set(stamp.id, stamp);
118
+ return stamp;
119
+ }
120
+ async save(stamp) {
121
+ await this._persist(stamp);
122
+ this._cache.set(stamp.id, stamp);
123
+ }
124
+ async load(id) {
125
+ const cached = this._cache.get(id);
126
+ if (cached)
127
+ return cached;
128
+ const json = await this._adapter.getById(id);
129
+ if (!json)
130
+ return null;
131
+ const stamp = MajikSignatureStamp.fromJSON(json);
132
+ this._cache.set(stamp.id, stamp);
133
+ return stamp;
134
+ }
135
+ async getStampOrThrow(id) {
136
+ const stamp = await this.load(id);
137
+ if (!stamp)
138
+ throw new MajikSignatureStampManagerError(`Stamp not found: ${id}`);
139
+ return stamp;
140
+ }
141
+ async loadAll() {
142
+ const all = await this._adapter.list();
143
+ for (const json of all) {
144
+ if (!this._cache.has(json.id)) {
145
+ try {
146
+ const stamp = MajikSignatureStamp.fromJSON(json);
147
+ this._cache.set(stamp.id, stamp);
148
+ }
149
+ catch (err) {
150
+ console.warn(`MajikSignatureStampManager.loadAll: skipping malformed stamp "${json?.id}":`, err);
151
+ }
152
+ }
153
+ }
154
+ return [...this._cache.values()];
155
+ }
156
+ async delete(id) {
157
+ await this._adapter.remove(id);
158
+ this._cache.delete(id);
159
+ }
160
+ async has(id) {
161
+ if (this._cache.has(id))
162
+ return true;
163
+ return this._adapter.exists(id);
164
+ }
165
+ get(id) {
166
+ return this._cache.get(id);
167
+ }
168
+ list() {
169
+ return [...this._cache.values()];
170
+ }
171
+ listByKind(kind) {
172
+ return this.list().filter((s) => s.kind === kind);
173
+ }
174
+ listByFingerprint(fingerprint) {
175
+ return this.list().filter((s) => s.fingerprint === fingerprint);
176
+ }
177
+ async query(query) {
178
+ if (!this._adapter.query) {
179
+ throw new MajikSignatureStampManagerError("query: current adapter does not support query()");
180
+ }
181
+ return this._adapter.query(query);
182
+ }
183
+ // ── Mutations ───────────────────────────────────────────────────────────
184
+ async rename(id, newName) {
185
+ const stamp = await this.getStampOrThrow(id);
186
+ stamp.rename(newName);
187
+ await this._persist(stamp);
188
+ return stamp;
189
+ }
190
+ /** Write-once at the MajikFile layer — id is preserved explicitly. */
191
+ async replaceContent(id, rawBytes, identity, mimeType) {
192
+ const existing = await this.getStampOrThrow(id);
193
+ const replacement = await MajikSignatureStamp.create({
194
+ id: existing.id,
195
+ data: rawBytes,
196
+ identity,
197
+ kind: existing.kind,
198
+ name: existing.name,
199
+ mimeType: mimeType ?? existing.mimeType ?? undefined,
200
+ });
201
+ await this._persist(replacement);
202
+ this._cache.set(replacement.id, replacement);
203
+ return replacement;
204
+ }
205
+ async decryptContent(id, identity) {
206
+ const stamp = await this.getStampOrThrow(id);
207
+ return stamp.decryptContent(identity);
208
+ }
209
+ async bulkRemove(ids) {
210
+ if (ids.length === 0)
211
+ return;
212
+ await this._adapter.bulkRemove(ids);
213
+ for (const id of ids)
214
+ this._cache.delete(id);
215
+ }
216
+ }
@@ -0,0 +1,64 @@
1
+ import { MajikFile } from "@majikah/majik-file";
2
+ import type { MajikFileDecryptIdentity, MajikFileIdentity, MajikFileJSON } from "@majikah/majik-file";
3
+ export type StampAssetKind = "image" | "audio" | "video" | "text";
4
+ export interface MajikSignatureStampJSON {
5
+ id: string;
6
+ kind: StampAssetKind;
7
+ name: string;
8
+ mimeType: string | null;
9
+ sizeBytes: number;
10
+ fingerprint: string;
11
+ data: string;
12
+ timestamp: string;
13
+ lastUpdate: string;
14
+ /** Full MajikFile metadata — required to reconstruct a working MajikFile
15
+ * instance via MajikFile.fromJSON() for decryptHydrate()/secureLock(). */
16
+ fileJson: MajikFileJSON;
17
+ }
18
+ export interface CreateStampOptions {
19
+ data: Uint8Array | ArrayBuffer;
20
+ identity: MajikFileIdentity;
21
+ kind: StampAssetKind;
22
+ name: string;
23
+ mimeType?: string;
24
+ /** Pass the existing id back in when replacing content on a stamp that
25
+ * already exists — MajikFile is write-once, so this is a *new* encrypted
26
+ * artifact under the hood, but the id stays stable for the caller. */
27
+ id?: string;
28
+ }
29
+ export declare class MajikSignatureStamp {
30
+ private readonly _id;
31
+ private readonly _kind;
32
+ private _name;
33
+ private readonly _mimeType;
34
+ private readonly _sizeBytes;
35
+ private readonly _fingerprint;
36
+ private readonly _timestamp;
37
+ private readonly _fileJson;
38
+ private _file;
39
+ private _lastUpdate;
40
+ private _encryptedBinary;
41
+ private constructor();
42
+ get id(): string;
43
+ get kind(): StampAssetKind;
44
+ get name(): string;
45
+ get mimeType(): string | null;
46
+ get sizeBytes(): number;
47
+ get fingerprint(): string;
48
+ get timestamp(): string;
49
+ get lastUpdate(): string;
50
+ static create(options: CreateStampOptions): Promise<MajikSignatureStamp>;
51
+ /** Plaintext-only rename — no re-encryption, no MajikFile involvement. */
52
+ rename(newName: string): void;
53
+ toJSON(): MajikSignatureStampJSON;
54
+ static fromJSON(json: MajikSignatureStampJSON): MajikSignatureStamp;
55
+ /** Reconstruct (or return the cached) MajikFile backing this stamp. */
56
+ toMajikFile(): MajikFile;
57
+ /** Cached decrypted bytes, if hydrateForFingerprint()/decryptHydrate() has
58
+ * already run for this stamp this session. Undefined otherwise, or after
59
+ * a lock. */
60
+ get decryptedBytes(): Uint8Array | undefined;
61
+ /** Accepts a full MajikKey OR a bare {fingerprint, mlKemSecretKey} —
62
+ * matches MajikFile's own MajikFileDecryptIdentity shape. */
63
+ decryptContent(identity: MajikFileDecryptIdentity): Promise<Uint8Array>;
64
+ }
@@ -0,0 +1,131 @@
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
+ _fileJson;
13
+ _file = null; // reconstructed lazily, cached per instance
14
+ _lastUpdate;
15
+ _encryptedBinary; // the .mjkb bytes — never the plaintext
16
+ constructor(json, binary) {
17
+ this._id = json.id;
18
+ this._kind = json.kind;
19
+ this._name = json.name;
20
+ this._mimeType = json.mimeType;
21
+ this._sizeBytes = json.sizeBytes;
22
+ this._fingerprint = json.fingerprint;
23
+ this._timestamp = json.timestamp;
24
+ this._lastUpdate = json.lastUpdate;
25
+ this._encryptedBinary = binary;
26
+ this._fileJson = json.fileJson;
27
+ }
28
+ get id() {
29
+ return this._id;
30
+ }
31
+ get kind() {
32
+ return this._kind;
33
+ }
34
+ get name() {
35
+ return this._name;
36
+ }
37
+ get mimeType() {
38
+ return this._mimeType;
39
+ }
40
+ get sizeBytes() {
41
+ return this._sizeBytes;
42
+ }
43
+ get fingerprint() {
44
+ return this._fingerprint;
45
+ }
46
+ get timestamp() {
47
+ return this._timestamp;
48
+ }
49
+ get lastUpdate() {
50
+ return this._lastUpdate;
51
+ }
52
+ // ── CREATE ──────────────────────────────────────────────────────────────
53
+ static async create(options) {
54
+ const { data, identity, kind, name, mimeType, id } = options;
55
+ if (!data)
56
+ throw new Error("MajikSignatureStamp.create: data is required");
57
+ if (!identity)
58
+ throw new Error("MajikSignatureStamp.create: identity is required");
59
+ if (!name?.trim())
60
+ throw new Error("MajikSignatureStamp.create: name is required");
61
+ const file = await MajikFile.create({
62
+ id,
63
+ data,
64
+ identity,
65
+ context: "user_upload",
66
+ originalName: name,
67
+ mimeType,
68
+ userId: identity.fingerprint,
69
+ });
70
+ const fileJson = file.toJSON();
71
+ const now = new Date().toISOString();
72
+ const binary = file.toBinaryBytes();
73
+ const json = {
74
+ id: fileJson.id,
75
+ kind,
76
+ name,
77
+ mimeType: fileJson.mime_type,
78
+ sizeBytes: fileJson.size_original,
79
+ fingerprint: identity.fingerprint,
80
+ data: arrayToBase64(binary),
81
+ timestamp: fileJson.timestamp ?? now,
82
+ lastUpdate: fileJson.last_update ?? now,
83
+ fileJson,
84
+ };
85
+ const instance = new MajikSignatureStamp(json, binary);
86
+ instance._file = file; // reuse — already fully constructed, no need to refetch
87
+ return instance;
88
+ }
89
+ /** Plaintext-only rename — no re-encryption, no MajikFile involvement. */
90
+ rename(newName) {
91
+ if (!newName?.trim())
92
+ throw new Error("rename: name must be non-empty");
93
+ this._name = newName.trim();
94
+ this._lastUpdate = new Date().toISOString();
95
+ }
96
+ toJSON() {
97
+ return {
98
+ id: this._id,
99
+ kind: this._kind,
100
+ name: this._name,
101
+ mimeType: this._mimeType,
102
+ sizeBytes: this._sizeBytes,
103
+ fingerprint: this._fingerprint,
104
+ data: arrayToBase64(this._encryptedBinary),
105
+ timestamp: this._timestamp,
106
+ lastUpdate: this._lastUpdate,
107
+ fileJson: this._fileJson,
108
+ };
109
+ }
110
+ static fromJSON(json) {
111
+ return new MajikSignatureStamp(json, base64ToUint8Array(json.data));
112
+ }
113
+ /** Reconstruct (or return the cached) MajikFile backing this stamp. */
114
+ toMajikFile() {
115
+ if (!this._file) {
116
+ this._file = MajikFile.fromJSON(this._fileJson, this._encryptedBinary);
117
+ }
118
+ return this._file;
119
+ }
120
+ /** Cached decrypted bytes, if hydrateForFingerprint()/decryptHydrate() has
121
+ * already run for this stamp this session. Undefined otherwise, or after
122
+ * a lock. */
123
+ get decryptedBytes() {
124
+ return this._file?.hasDecryptedFile ? this._file.decryptedFile : undefined;
125
+ }
126
+ /** Accepts a full MajikKey OR a bare {fingerprint, mlKemSecretKey} —
127
+ * matches MajikFile's own MajikFileDecryptIdentity shape. */
128
+ async decryptContent(identity) {
129
+ return MajikFile.decrypt(this._encryptedBinary, identity);
130
+ }
131
+ }
@@ -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,3 @@
1
+ import { MajikSignatureStampJSON } from "../../stamp/majik-signature-stamp";
2
+ import { MajikStorageAdapter } from "../storage-adapter";
3
+ export type MajikSignatureStampStorageAdapter = MajikStorageAdapter<MajikSignatureStampJSON>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { MajikSignatureStampJSON } from "../../stamp/majik-signature-stamp";
2
+ import { IDBGenericAdapter } from "../idb-adapter";
3
+ export declare const IDB_ADAPTER_STAMPSTORE: IDBGenericAdapter<MajikSignatureStampJSON>;
@@ -0,0 +1,5 @@
1
+ import { IDBGenericAdapter } from "../idb-adapter";
2
+ const IDB_DB_NAME = "majik-signature-stamps";
3
+ const IDB_STORE_NAME = "stamps";
4
+ const IDB_VERSION = 1;
5
+ export const IDB_ADAPTER_STAMPSTORE = new IDBGenericAdapter(IDB_DB_NAME, IDB_STORE_NAME, IDB_VERSION);
@@ -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
+ }