sliftutils 1.5.0 → 1.5.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.
package/index.d.ts CHANGED
@@ -78,11 +78,20 @@ declare module "sliftutils/misc/https/certs" {
78
78
  certB64: string;
79
79
  keyB64: string;
80
80
  };
81
+ export type BrowserIdentityStorageType = {
82
+ domain: string;
83
+ certB64: string;
84
+ privateKey: CryptoKey;
85
+ };
81
86
  export interface X509KeyPair {
82
87
  domain: string;
83
88
  cert: Buffer;
84
- key: Buffer;
89
+ key: Buffer | CryptoKey;
85
90
  }
91
+ export type NonExtractableKeyPair = {
92
+ publicKeyBytes: Buffer;
93
+ privateKey: CryptoKey;
94
+ };
86
95
  export declare function getCommonName(cert: Buffer | string): string;
87
96
  export declare function createX509(config: {
88
97
  domain: string;
@@ -91,23 +100,24 @@ declare module "sliftutils/misc/https/certs" {
91
100
  keyPair: {
92
101
  publicKey: forge.Ed25519PublicKey;
93
102
  privateKey: forge.Ed25519PrivateKey;
94
- } | forge.pki.KeyPair;
95
- }): X509KeyPair;
103
+ } | forge.pki.KeyPair | NonExtractableKeyPair;
104
+ }): MaybePromise<X509KeyPair>;
105
+ export declare function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair>;
96
106
  export declare function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey): string;
97
107
  export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
98
108
  export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
99
109
  export declare const sign: (keyPair: {
100
- key: string | Buffer;
101
- }, data: unknown) => string;
110
+ key: string | Buffer | CryptoKey;
111
+ }, data: unknown) => MaybePromise<string>;
102
112
  export declare function verify(cert: string, signature: string, data: unknown): void;
103
113
  export declare function validateCACert(domain: string, cert: string | Buffer): void;
104
114
  export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
105
115
  export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
106
116
  export declare function generateRSAKeyPair(): forge.pki.rsa.KeyPair;
107
- export declare function generateTestCA(domain: string): X509KeyPair;
117
+ export declare function generateTestCA(domain: string): MaybePromise<X509KeyPair>;
108
118
  export declare function createCertFromCA(config: {
109
119
  CAKeyPair: X509KeyPair;
110
- }): X509KeyPair;
120
+ }): MaybePromise<X509KeyPair>;
111
121
  export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
112
122
  export type NodeIdParts = {
113
123
  threadId: string;
@@ -129,7 +139,7 @@ declare module "sliftutils/misc/https/certs" {
129
139
  machineId: string;
130
140
  publicKey: Buffer;
131
141
  }): boolean;
132
- export declare function getThreadKeyCert(domain: string): X509KeyPair;
142
+ export declare function getThreadKeyCert(domain: string): MaybePromise<X509KeyPair>;
133
143
  export declare const createTestBrowserKeyCert: {
134
144
  (): Promise<X509KeyPair>;
135
145
  reset(): void;
@@ -213,11 +223,18 @@ declare module "sliftutils/misc/https/node-forge-ed25519" {
213
223
  static publicKeyToPem(key: Ed25519PublicKey): string;
214
224
  static publicKeyFromPem(pem: string): Ed25519PublicKey;
215
225
  }
226
+ namespace pki {
227
+ function getTBSCertificate(cert: pki.Certificate): asn1.Asn1;
228
+ }
216
229
  }
217
230
  }
218
231
 
219
232
  declare module "sliftutils/misc/https/persistentLocalStorage" {
220
233
  import { MaybePromise } from "socket-function/src/types";
234
+ export declare function getIDBKeyStore<T>(appName: string, key: string): {
235
+ get(): Promise<T | undefined>;
236
+ set(value: T | undefined): Promise<void>;
237
+ };
221
238
  export declare function getKeyStore<T>(appName: string, key: string): {
222
239
  get(): MaybePromise<T | undefined>;
223
240
  set(value: T | null): MaybePromise<void>;
@@ -11,11 +11,20 @@ export type IdentityStorageType = {
11
11
  certB64: string;
12
12
  keyB64: string;
13
13
  };
14
+ export type BrowserIdentityStorageType = {
15
+ domain: string;
16
+ certB64: string;
17
+ privateKey: CryptoKey;
18
+ };
14
19
  export interface X509KeyPair {
15
20
  domain: string;
16
21
  cert: Buffer;
17
- key: Buffer;
22
+ key: Buffer | CryptoKey;
18
23
  }
24
+ export type NonExtractableKeyPair = {
25
+ publicKeyBytes: Buffer;
26
+ privateKey: CryptoKey;
27
+ };
19
28
  export declare function getCommonName(cert: Buffer | string): string;
20
29
  export declare function createX509(config: {
21
30
  domain: string;
@@ -24,23 +33,24 @@ export declare function createX509(config: {
24
33
  keyPair: {
25
34
  publicKey: forge.Ed25519PublicKey;
26
35
  privateKey: forge.Ed25519PrivateKey;
27
- } | forge.pki.KeyPair;
28
- }): X509KeyPair;
36
+ } | forge.pki.KeyPair | NonExtractableKeyPair;
37
+ }): MaybePromise<X509KeyPair>;
38
+ export declare function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair>;
29
39
  export declare function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey): string;
30
40
  export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate;
31
41
  export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
32
42
  export declare const sign: (keyPair: {
33
- key: string | Buffer;
34
- }, data: unknown) => string;
43
+ key: string | Buffer | CryptoKey;
44
+ }, data: unknown) => MaybePromise<string>;
35
45
  export declare function verify(cert: string, signature: string, data: unknown): void;
36
46
  export declare function validateCACert(domain: string, cert: string | Buffer): void;
37
47
  export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
38
48
  export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
39
49
  export declare function generateRSAKeyPair(): forge.pki.rsa.KeyPair;
40
- export declare function generateTestCA(domain: string): X509KeyPair;
50
+ export declare function generateTestCA(domain: string): MaybePromise<X509KeyPair>;
41
51
  export declare function createCertFromCA(config: {
42
52
  CAKeyPair: X509KeyPair;
43
- }): X509KeyPair;
53
+ }): MaybePromise<X509KeyPair>;
44
54
  export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
45
55
  export type NodeIdParts = {
46
56
  threadId: string;
@@ -62,7 +72,7 @@ export declare function verifyMachineIdForPublicKey(config: {
62
72
  machineId: string;
63
73
  publicKey: Buffer;
64
74
  }): boolean;
65
- export declare function getThreadKeyCert(domain: string): X509KeyPair;
75
+ export declare function getThreadKeyCert(domain: string): MaybePromise<X509KeyPair>;
66
76
  export declare const createTestBrowserKeyCert: {
67
77
  (): Promise<X509KeyPair>;
68
78
  reset(): void;
@@ -18,7 +18,7 @@ import { getNodeIdDomain, getNodeIdDomainMaybeUndefined, getNodeIdLocation } fro
18
18
  import { MaybePromise } from "socket-function/src/types";
19
19
  import { SocketFunction } from "socket-function/SocketFunction";
20
20
  import { resetAllNodeCallFactories } from "socket-function/src/nodeCache";
21
- import { getKeyStore } from "./persistentLocalStorage";
21
+ import { getKeyStore, getIDBKeyStore } from "./persistentLocalStorage";
22
22
  import { ellipsize } from "../strings";
23
23
 
24
24
  setFlag(require, "node-forge", "allowclient", true);
@@ -30,12 +30,20 @@ export const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
30
30
 
31
31
  export const identityStorageKey = "machineCA_11";
32
32
  export type IdentityStorageType = { domain: string; certB64: string; keyB64: string };
33
+ // In the browser the private key is a non-extractable CryptoKey. IndexedDB structured-clones
34
+ // it, so it persists, but the raw key bytes can never be read by JavaScript (only used
35
+ // via crypto.subtle.sign). This protects against exfiltration of stored keys.
36
+ export type BrowserIdentityStorageType = { domain: string; certB64: string; privateKey: CryptoKey };
33
37
 
34
38
  function getIdentityStore(domain: string) {
35
39
  return getKeyStore<IdentityStorageType>(domain, identityStorageKey);
36
40
  }
41
+ function getBrowserIdentityStore(domain: string) {
42
+ return getIDBKeyStore<BrowserIdentityStorageType>(domain, identityStorageKey);
43
+ }
37
44
 
38
- export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer; }
45
+ export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer | CryptoKey; }
46
+ export type NonExtractableKeyPair = { publicKeyBytes: Buffer; privateKey: CryptoKey };
39
47
 
40
48
  export function getCommonName(cert: Buffer | string) {
41
49
  let subject = new crypto.X509Certificate(cert).subject;
@@ -53,14 +61,18 @@ export function createX509(
53
61
  keyPair: {
54
62
  publicKey: forge.Ed25519PublicKey;
55
63
  privateKey: forge.Ed25519PrivateKey;
56
- } | forge.pki.KeyPair;
64
+ } | forge.pki.KeyPair | NonExtractableKeyPair;
57
65
  }
58
- ): X509KeyPair {
59
- return measureBlock(function createX509() {
66
+ ): MaybePromise<X509KeyPair> {
67
+ return measureBlock(function createX509(): MaybePromise<X509KeyPair> {
60
68
  let { domain, issuer, lifeSpan, keyPair } = config;
61
69
 
62
70
  let certObj = forge.pki.createCertificate();
63
- certObj.publicKey = keyPair.publicKey;
71
+ if ("publicKeyBytes" in keyPair) {
72
+ certObj.publicKey = { publicKeyBytes: keyPair.publicKeyBytes, keyType: forge.pki.oids["EdDSA25519"] } as unknown as forge.pki.PublicKey;
73
+ } else {
74
+ certObj.publicKey = keyPair.publicKey;
75
+ }
64
76
  certObj.serialNumber = "01";
65
77
  // Give it 5 minutes before now. If we give it too much time, it can look like the cert is really
66
78
  // old, which will trigger various processes to try to get a fresher one (as if it lasts for
@@ -115,7 +127,18 @@ export function createX509(
115
127
  certObj.setExtensions(extensions);
116
128
 
117
129
 
118
- measureBlock(function sign() {
130
+ let signResult = measureBlock(function sign(): MaybePromise<void> {
131
+ let signingCryptoKey: CryptoKey | undefined;
132
+ if (issuer === "self") {
133
+ if ("publicKeyBytes" in keyPair) {
134
+ signingCryptoKey = keyPair.privateKey;
135
+ }
136
+ } else if (issuer.key instanceof CryptoKey) {
137
+ signingCryptoKey = issuer.key;
138
+ }
139
+ if (signingCryptoKey) {
140
+ return signCertWithCryptoKey(certObj, signingCryptoKey);
141
+ }
119
142
  if (issuer === "self") {
120
143
  certObj.sign(keyPair.privateKey as any, forge.md.sha256.create());
121
144
  } else {
@@ -123,15 +146,44 @@ export function createX509(
123
146
  }
124
147
  });
125
148
 
126
- return measureBlock(function toPems() {
127
- return {
128
- domain,
129
- cert: Buffer.from(forge.pki.certificateToPem(certObj)),
130
- key: Buffer.from(privateKeyToPem(keyPair.privateKey)),
131
- };
132
- });
149
+ function toPems() {
150
+ return measureBlock(function toPems() {
151
+ let key: Buffer | CryptoKey;
152
+ if ("publicKeyBytes" in keyPair) {
153
+ key = keyPair.privateKey;
154
+ } else {
155
+ key = Buffer.from(privateKeyToPem(keyPair.privateKey));
156
+ }
157
+ return {
158
+ domain,
159
+ cert: Buffer.from(forge.pki.certificateToPem(certObj)),
160
+ key,
161
+ };
162
+ });
163
+ }
164
+ if (signResult instanceof Promise) {
165
+ return signResult.then(toPems);
166
+ }
167
+ return toPems();
133
168
  });
134
169
  }
170
+
171
+ // Replicates the forked forge's ed25519 cert.sign, except the signature is produced by
172
+ // crypto.subtle, so the private key can be a non-extractable CryptoKey.
173
+ async function signCertWithCryptoKey(certObj: forge.pki.Certificate, privateKey: CryptoKey): Promise<void> {
174
+ certObj.signatureOid = certObj.siginfo.algorithmOid = forge.pki.oids["EdDSA25519"];
175
+ let tbs = forge.pki.getTBSCertificate(certObj);
176
+ let tbsDer = forge.asn1.toDer(tbs).getBytes();
177
+ let signature = await globalThis.crypto.subtle.sign("Ed25519", privateKey, Buffer.from(tbsDer, "binary"));
178
+ certObj.tbsCertificate = tbs;
179
+ certObj.signature = Buffer.from(signature).toString("binary");
180
+ }
181
+
182
+ export async function generateNonExtractableKeyPair(): Promise<NonExtractableKeyPair> {
183
+ let keyPair = await globalThis.crypto.subtle.generateKey("Ed25519", false, ["sign", "verify"]) as CryptoKeyPair;
184
+ let publicKeyBytes = Buffer.from(await globalThis.crypto.subtle.exportKey("raw", keyPair.publicKey));
185
+ return { publicKeyBytes, privateKey: keyPair.privateKey };
186
+ }
135
187
  export function privateKeyToPem(buffer: forge.pki.PrivateKey | forge.Ed25519PrivateKey) {
136
188
  if ("privateKeyBytes" in buffer) {
137
189
  return forge.ed25519.privateKeyToPem(buffer);
@@ -173,8 +225,13 @@ function isED25519(key: string | Buffer) {
173
225
  }
174
226
 
175
227
  // EQUIVALENT TO: `crypto.createSign("SHA256").update(JSON.stringify(payload)).sign(keyCert.key, "binary")`
176
- export const sign = measureWrap(function sign(keyPair: { key: string | Buffer }, data: unknown): string {
228
+ export const sign = measureWrap(function sign(keyPair: { key: string | Buffer | CryptoKey }, data: unknown): MaybePromise<string> {
177
229
  let dataStr = JSON.stringify(data);
230
+ if (keyPair.key instanceof CryptoKey) {
231
+ // "binary" encoding matches how forge interprets the string on the verify side
232
+ return globalThis.crypto.subtle.sign("Ed25519", keyPair.key, Buffer.from(dataStr, "binary"))
233
+ .then(signature => Buffer.from(signature).toString("binary"));
234
+ }
178
235
  if (isED25519(keyPair.key)) {
179
236
  let privateKey = (forge.pki.ed25519 as any).privateKeyFromPem(keyPair.key.toString());
180
237
  return privateKey.sign(dataStr);
@@ -384,6 +441,31 @@ export function generateTestCA(domain: string) {
384
441
 
385
442
  let identityCA = cache((domain: string) => {
386
443
  let identityCA = lazy((async (): Promise<X509KeyPair> => {
444
+ if (!isNode()) {
445
+ let store = getBrowserIdentityStore(domain);
446
+ let caCached = await store.get();
447
+ if (!caCached) {
448
+ console.log(`Generating new identity CA (non-extractable browser key)`);
449
+ const keyPair = await generateNonExtractableKeyPair();
450
+ let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKeyBytes);
451
+ let fullDomain = `${caPublicKeyPart}.${domain}`;
452
+ let value = await createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
453
+ caCached = {
454
+ domain: value.domain,
455
+ certB64: value.cert.toString("base64"),
456
+ privateKey: keyPair.privateKey,
457
+ };
458
+ await store.set(caCached);
459
+ }
460
+ let result = {
461
+ domain: caCached.domain,
462
+ cert: Buffer.from(caCached.certB64, "base64"),
463
+ key: caCached.privateKey,
464
+ };
465
+ trustCertificate(result.cert.toString());
466
+ identityCA.set(result);
467
+ return result;
468
+ }
387
469
  let identityCACached = getIdentityStore(domain);
388
470
  let caCached = await identityCACached.get();
389
471
  if (!caCached) {
@@ -391,16 +473,13 @@ let identityCA = cache((domain: string) => {
391
473
  const keyPair = generateKeyPair();
392
474
  let caPublicKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
393
475
  let fullDomain = `${caPublicKeyPart}.${domain}`;
394
- if (!isNode()) {
395
- fullDomain = `${caPublicKeyPart}.${domain}`;
396
- }
397
476
 
398
- let value = createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
477
+ let value = await createX509({ domain: fullDomain, issuer: "self", keyPair, lifeSpan: timeInDay * 365 * 20 });
399
478
 
400
479
  caCached = {
401
480
  domain: value.domain,
402
481
  certB64: value.cert.toString("base64"),
403
- keyB64: value.key.toString("base64"),
482
+ keyB64: keyPairKeyToBuffer(value.key).toString("base64"),
404
483
  };
405
484
  await identityCACached.set(caCached);
406
485
  }
@@ -416,6 +495,13 @@ let identityCA = cache((domain: string) => {
416
495
  return identityCA;
417
496
  });
418
497
 
498
+ function keyPairKeyToBuffer(key: Buffer | CryptoKey): Buffer {
499
+ if (key instanceof CryptoKey) {
500
+ throw new Error(`Cannot serialize a non-extractable CryptoKey`);
501
+ }
502
+ return key;
503
+ }
504
+
419
505
  // IMPORTANT! We do not embed any debug info in this domain. If we did, it would be useful,
420
506
  // but... potentally a security vulnerability, as if the debug info (such as a prefix)
421
507
  // is used to identify what a certificate is for, it would be easy for an attack to
@@ -424,9 +510,22 @@ let identityCA = cache((domain: string) => {
424
510
  // (and hopefully stored in a UI, showing IP, time, etc).
425
511
  export function createCertFromCA(config: {
426
512
  CAKeyPair: X509KeyPair;
427
- }): X509KeyPair {
428
- return measureBlock(function createCertFromCA() {
513
+ }): MaybePromise<X509KeyPair> {
514
+ return measureBlock(function createCertFromCA(): MaybePromise<X509KeyPair> {
429
515
  let { CAKeyPair } = config;
516
+ if (!isNode()) {
517
+ return (async () => {
518
+ const keyPair = await generateNonExtractableKeyPair();
519
+ let domainKeyPart = getDomainPartFromPublicKey(keyPair.publicKeyBytes);
520
+ let fullDomain = `${domainKeyPart}.${CAKeyPair.domain}`;
521
+ return await createX509({
522
+ domain: fullDomain,
523
+ issuer: CAKeyPair,
524
+ keyPair,
525
+ lifeSpan: timeInDay * 365 * 10,
526
+ });
527
+ })();
528
+ }
430
529
  const keyPair = generateKeyPair();
431
530
  let domainKeyPart = getDomainPartFromPublicKey(keyPair.publicKey);
432
531
  let fullDomain = `${domainKeyPart}.${config.CAKeyPair.domain}`;
@@ -491,12 +590,27 @@ export function encodeNodeId(parts: NodeIdParts) {
491
590
  }
492
591
 
493
592
  export async function setIdentityCARaw(domain: string, json: string) {
494
- let identityCACached = getIdentityStore(domain);
495
593
  let obj = JSON.parse(json) as {
496
594
  domain: string;
497
595
  certB64: string;
498
596
  keyB64: string;
499
597
  };
598
+ if (!isNode()) {
599
+ // Import into a non-extractable CryptoKey, so once stored the key can't be exfiltrated
600
+ let privateKey = await importEd25519PrivateKey(Buffer.from(obj.keyB64, "base64").toString());
601
+ let ca = {
602
+ domain: obj.domain,
603
+ cert: Buffer.from(obj.certB64, "base64"),
604
+ key: privateKey,
605
+ };
606
+ trustCertificate(ca.cert.toString());
607
+ identityCA(domain).set(ca);
608
+ getThreadKeyCertBase(domain).reset();
609
+ await getBrowserIdentityStore(domain).set({ domain: obj.domain, certB64: obj.certB64, privateKey });
610
+ resetAllNodeCallFactories();
611
+ return;
612
+ }
613
+ let identityCACached = getIdentityStore(domain);
500
614
  let ca = {
501
615
  domain: obj.domain,
502
616
  cert: Buffer.from(obj.certB64, "base64"),
@@ -509,6 +623,21 @@ export async function setIdentityCARaw(domain: string, json: string) {
509
623
  resetAllNodeCallFactories();
510
624
  }
511
625
 
626
+ async function importEd25519PrivateKey(pem: string): Promise<CryptoKey> {
627
+ let parsed = forge.ed25519.privateKeyFromPem(pem);
628
+ let bytes = Buffer.from(parsed.privateKeyBytes || parsed);
629
+ let seed = bytes;
630
+ if (seed.length === 64) {
631
+ // NaCl layout is seed || publicKey
632
+ seed = seed.subarray(0, 32);
633
+ }
634
+ if (seed.length !== 32) {
635
+ throw new Error(`Expected a 32 or 64 byte ed25519 private key, was ${bytes.length} bytes`);
636
+ }
637
+ let pkcs8 = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
638
+ return await globalThis.crypto.subtle.importKey("pkcs8", pkcs8, "Ed25519", false, ["sign"]);
639
+ }
640
+
512
641
  export async function loadIdentityCA(domain: string) {
513
642
  await identityCA(domain)();
514
643
  }
@@ -531,7 +660,11 @@ export function getOwnMachineId(domain: string) {
531
660
  return getMachineId(getIdentityCA(domain).domain, domain);
532
661
  }
533
662
  export function getOwnThreadId(domain: string) {
534
- return decodeNodeIdAssert(getThreadKeyCert(domain).domain, domain).threadId;
663
+ let threadKeyCert = getThreadKeyCert(domain);
664
+ if (threadKeyCert instanceof Promise) {
665
+ throw new Error("Thread key cert is not yet loaded. Await getThreadKeyCert() in your startup before accessing the threadId");
666
+ }
667
+ return decodeNodeIdAssert(threadKeyCert.domain, domain).threadId;
535
668
  }
536
669
 
537
670
  /** Part of the machineId comes from the publicKey, so we can use it to verify */
@@ -14,4 +14,7 @@ declare module "node-forge" {
14
14
  static publicKeyToPem(key: Ed25519PublicKey): string;
15
15
  static publicKeyFromPem(pem: string): Ed25519PublicKey;
16
16
  }
17
+ namespace pki {
18
+ function getTBSCertificate(cert: pki.Certificate): asn1.Asn1;
19
+ }
17
20
  }
@@ -1,4 +1,8 @@
1
1
  import { MaybePromise } from "socket-function/src/types";
2
+ export declare function getIDBKeyStore<T>(appName: string, key: string): {
3
+ get(): Promise<T | undefined>;
4
+ set(value: T | undefined): Promise<void>;
5
+ };
2
6
  export declare function getKeyStore<T>(appName: string, key: string): {
3
7
  get(): MaybePromise<T | undefined>;
4
8
  set(value: T | null): MaybePromise<void>;
@@ -4,6 +4,42 @@ import os from "os";
4
4
  import { MaybePromise } from "socket-function/src/types";
5
5
  import { cache } from "socket-function/src/caching";
6
6
 
7
+ // Stores structured-cloneable values (including non-extractable CryptoKeys) in IndexedDB.
8
+ export function getIDBKeyStore<T>(appName: string, key: string): {
9
+ get(): Promise<T | undefined>;
10
+ set(value: T | undefined): Promise<void>;
11
+ } {
12
+ async function withStore<R>(mode: IDBTransactionMode, fnc: (store: IDBObjectStore) => IDBRequest<R>): Promise<R> {
13
+ let openReq = indexedDB.open(`keystore_${appName}`, 1);
14
+ openReq.onupgradeneeded = () => openReq.result.createObjectStore("kv");
15
+ let db = await new Promise<IDBDatabase>((resolve, reject) => {
16
+ openReq.onsuccess = () => resolve(openReq.result);
17
+ openReq.onerror = () => reject(openReq.error);
18
+ });
19
+ try {
20
+ let req = fnc(db.transaction("kv", mode).objectStore("kv"));
21
+ return await new Promise<R>((resolve, reject) => {
22
+ req.onsuccess = () => resolve(req.result);
23
+ req.onerror = () => reject(req.error);
24
+ });
25
+ } finally {
26
+ db.close();
27
+ }
28
+ }
29
+ return {
30
+ async get() {
31
+ return await withStore("readonly", store => store.get(key)) as T | undefined;
32
+ },
33
+ async set(value: T | undefined) {
34
+ if (!value) {
35
+ await withStore("readwrite", store => store.delete(key));
36
+ return;
37
+ }
38
+ await withStore("readwrite", store => store.put(value, key));
39
+ },
40
+ };
41
+ }
42
+
7
43
  export function getKeyStore<T>(appName: string, key: string): {
8
44
  get(): MaybePromise<T | undefined>;
9
45
  set(value: T | null): MaybePromise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -53,6 +53,7 @@
53
53
  "acme-client": "^5.0.0",
54
54
  "js-sha256": "^0.11.1",
55
55
  "mobx": "^6.13.3",
56
+ "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
56
57
  "preact-old-types": "^10.28.1",
57
58
  "shell-quote": "^1.8.3",
58
59
  "socket-function": "^1.1.42",
package/yarn.lock CHANGED
@@ -1631,6 +1631,10 @@ node-forge@^1.3.1:
1631
1631
  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751"
1632
1632
  integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==
1633
1633
 
1634
+ "node-forge@https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6":
1635
+ version "1.3.2-0"
1636
+ resolved "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6"
1637
+
1634
1638
  node-gyp-build-optional-packages@5.1.1:
1635
1639
  version "5.1.1"
1636
1640
  resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz#52b143b9dd77b7669073cbfe39e3f4118bfc603c"
@@ -1940,10 +1944,10 @@ slash@^3.0.0:
1940
1944
  resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
1941
1945
  integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
1942
1946
 
1943
- socket-function@^1.1.40:
1944
- version "1.1.40"
1945
- resolved "https://registry.yarnpkg.com/socket-function/-/socket-function-1.1.40.tgz#0994aa018b3fcc64a131d29e5926377b7baff280"
1946
- integrity sha512-PtU8csq90nbjM/Jr/irRHzj2PojUgcMXTctfBMDEHJrLKmeOM4flf87svn0M8ad5iwNiSyav8SdxCzCl9IwZJw==
1947
+ socket-function@^1.1.42:
1948
+ version "1.1.47"
1949
+ resolved "https://registry.yarnpkg.com/socket-function/-/socket-function-1.1.47.tgz#45cb508b4f402ae37c3053eb8b7cabe1cefaa011"
1950
+ integrity sha512-DT+TWWcr6GPQ6E/f8o3p+3UZlHfgnLPCJ6baSY+7jOOm5EdoPXcaCF5L4j4cna4OV4Tpw3ny85b4BqvixXP4sQ==
1947
1951
  dependencies:
1948
1952
  "@types/pako" "^2.0.3"
1949
1953
  "@types/ws" "^8.5.3"