strata-storage 2.6.1 → 2.7.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/AI-INTEGRATION-GUIDE.md +129 -11
- package/README.md +42 -64
- package/android/AGENTS.md +18 -3
- package/android/CLAUDE.md +36 -3
- package/android/build.gradle +5 -1
- package/android/src/main/java/com/strata/storage/EncryptedStorage.java +31 -18
- package/android/src/main/java/com/strata/storage/FilesystemStorage.java +62 -10
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +65 -13
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +63 -51
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +80 -11
- package/dist/README.md +42 -64
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/FilesystemAdapter.js +6 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/PreferencesAdapter.js +6 -2
- package/dist/adapters/capacitor/SecureAdapter.d.ts +3 -3
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SecureAdapter.js +6 -2
- package/dist/adapters/capacitor/SqliteAdapter.d.ts +24 -3
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SqliteAdapter.js +105 -23
- package/dist/adapters/capacitor/index.d.ts +4 -4
- package/dist/adapters/web/CacheAdapter.d.ts +2 -2
- package/dist/adapters/web/CacheAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CacheAdapter.js +20 -5
- package/dist/adapters/web/CookieAdapter.d.ts +2 -2
- package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CookieAdapter.js +27 -3
- package/dist/adapters/web/IndexedDBAdapter.d.ts +10 -3
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
- package/dist/adapters/web/IndexedDBAdapter.js +19 -4
- package/dist/adapters/web/LocalStorageAdapter.d.ts +2 -2
- package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/LocalStorageAdapter.js +15 -1
- package/dist/adapters/web/MemoryAdapter.d.ts +9 -2
- package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
- package/dist/adapters/web/MemoryAdapter.js +22 -5
- package/dist/adapters/web/SessionStorageAdapter.d.ts +10 -10
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/SessionStorageAdapter.js +17 -3
- package/dist/adapters/web/URLAdapter.d.ts +2 -2
- package/dist/adapters/web/index.d.ts +7 -7
- package/dist/capacitor.d.ts +6 -6
- package/dist/capacitor.d.ts.map +1 -1
- package/dist/capacitor.js +5 -4
- package/dist/core/AdapterRegistry.d.ts +1 -1
- package/dist/core/BaseAdapter.d.ts +21 -5
- package/dist/core/BaseAdapter.d.ts.map +1 -1
- package/dist/core/BaseAdapter.js +29 -1
- package/dist/core/Strata.d.ts +53 -7
- package/dist/core/Strata.d.ts.map +1 -1
- package/dist/core/Strata.js +581 -125
- package/dist/features/compression/index.d.ts +1 -1
- package/dist/features/compression.d.ts +28 -9
- package/dist/features/compression.d.ts.map +1 -1
- package/dist/features/compression.js +164 -77
- package/dist/features/encryption/index.d.ts +1 -1
- package/dist/features/encryption.d.ts +55 -2
- package/dist/features/encryption.d.ts.map +1 -1
- package/dist/features/encryption.js +139 -26
- package/dist/features/migration.d.ts +1 -1
- package/dist/features/migration.d.ts.map +1 -1
- package/dist/features/migration.js +7 -2
- package/dist/features/observer/index.d.ts +1 -1
- package/dist/features/observer.d.ts +1 -1
- package/dist/features/query/index.d.ts +1 -1
- package/dist/features/query.d.ts +2 -1
- package/dist/features/query.d.ts.map +1 -1
- package/dist/features/query.js +32 -7
- package/dist/features/sync/index.d.ts +1 -1
- package/dist/features/sync.d.ts +19 -3
- package/dist/features/sync.d.ts.map +1 -1
- package/dist/features/sync.js +75 -12
- package/dist/features/ttl/index.d.ts +1 -1
- package/dist/features/ttl.d.ts +2 -2
- package/dist/features/ttl.d.ts.map +1 -1
- package/dist/features/ttl.js +4 -2
- package/dist/firebase.d.ts +1 -1
- package/dist/firebase.d.ts.map +1 -1
- package/dist/firebase.js +72 -10
- package/dist/index.d.ts +24 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/integrations/angular/index.d.ts +2 -2
- package/dist/integrations/angular/index.d.ts.map +1 -1
- package/dist/integrations/angular/index.js +7 -8
- package/dist/integrations/react/index.d.ts +3 -3
- package/dist/integrations/react/index.d.ts.map +1 -1
- package/dist/integrations/react/index.js +16 -4
- package/dist/integrations/vue/index.d.ts +2 -2
- package/dist/integrations/vue/index.d.ts.map +1 -1
- package/dist/integrations/vue/index.js +5 -2
- package/dist/package.json +11 -2
- package/dist/plugin/definitions.d.ts +29 -3
- package/dist/plugin/definitions.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/web.d.ts +11 -2
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +23 -4
- package/dist/types/index.d.ts +50 -20
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -10
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +7 -0
- package/ios/AGENTS.md +14 -3
- package/ios/CLAUDE.md +25 -3
- package/ios/Plugin/FilesystemStorage.swift +19 -0
- package/ios/Plugin/SQLiteStorage.swift +59 -9
- package/ios/Plugin/StrataStoragePlugin.m +1 -0
- package/ios/Plugin/StrataStoragePlugin.swift +87 -34
- package/ios/Plugin/UserDefaultsStorage.swift +47 -46
- package/package.json +27 -24
- package/scripts/build.js +46 -27
- package/scripts/cli.js +1 -1
- package/scripts/configure.js +2 -2
- package/scripts/postinstall.js +3 -2
- package/dist/android/AGENTS.md +0 -51
- package/dist/android/CLAUDE.md +0 -89
- package/dist/android/build.gradle +0 -58
- package/dist/android/proguard-rules.pro +0 -11
- package/dist/android/settings.gradle +0 -2
- package/dist/android/src/main/AndroidManifest.xml +0 -2
- package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +0 -218
- package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +0 -287
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +0 -371
- package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +0 -209
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +0 -616
- package/dist/core/StorageStrategy.d.ts +0 -55
- package/dist/core/StorageStrategy.d.ts.map +0 -1
- package/dist/core/StorageStrategy.js +0 -200
- package/dist/ios/AGENTS.md +0 -48
- package/dist/ios/CLAUDE.md +0 -84
- package/dist/ios/Plugin/FilesystemStorage.swift +0 -218
- package/dist/ios/Plugin/KeychainStorage.swift +0 -233
- package/dist/ios/Plugin/SQLiteStorage.swift +0 -426
- package/dist/ios/Plugin/StrataStoragePlugin.m +0 -23
- package/dist/ios/Plugin/StrataStoragePlugin.swift +0 -411
- package/dist/ios/Plugin/UserDefaultsStorage.swift +0 -87
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "../compression.js";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* Compression configuration
|
|
7
7
|
*/
|
|
8
8
|
export interface CompressionConfig {
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Compression algorithm. Only the bundled zero-dependency LZ codec is
|
|
11
|
+
* implemented (`'lz'`); the field is kept for forward compatibility.
|
|
12
|
+
*/
|
|
13
|
+
algorithm?: 'lz';
|
|
10
14
|
threshold?: number;
|
|
11
15
|
level?: number;
|
|
12
16
|
}
|
|
@@ -37,22 +41,37 @@ export declare class CompressionManager {
|
|
|
37
41
|
* Check if data is compressed
|
|
38
42
|
*/
|
|
39
43
|
isCompressedData(data: unknown): data is CompressedData;
|
|
44
|
+
private static readonly FORMAT;
|
|
45
|
+
private static readonly LZW_MAX_CODE;
|
|
40
46
|
/**
|
|
41
|
-
* LZ
|
|
47
|
+
* LZ compression over UTF-8 bytes.
|
|
48
|
+
*
|
|
49
|
+
* The alphabet is bytes (0–255), so the literal-vs-dictionary-code
|
|
50
|
+
* discriminator (`< 256`) on decompress is correct for ALL input — including
|
|
51
|
+
* CJK / emoji / symbols whose code points are ≥ 256. (Operating on raw UTF-16
|
|
52
|
+
* chars previously misclassified any such literal as a dictionary code and
|
|
53
|
+
* silently corrupted the stored value, or threw on read.)
|
|
42
54
|
*/
|
|
43
55
|
private lzCompress;
|
|
44
56
|
/**
|
|
45
|
-
* LZ
|
|
57
|
+
* LZ decompression — inverse of {@link lzCompress}, reconstructing UTF-8 bytes
|
|
58
|
+
* then decoding them back to the original string.
|
|
46
59
|
*/
|
|
47
60
|
private lzDecompress;
|
|
61
|
+
private static readonly B64_CHARS;
|
|
62
|
+
/** Base64-encode raw bytes (btoa when available, else a pure-JS fallback). */
|
|
63
|
+
private bytesToBase64;
|
|
64
|
+
/** Decode a base64 string back to raw bytes (atob when available, else pure-JS). */
|
|
65
|
+
private base64ToBytes;
|
|
48
66
|
/**
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*
|
|
67
|
+
* Legacy decoder for payloads written by <= 2.7.0 (algorithm 'lz'): the
|
|
68
|
+
* original UTF-16-char LZW over the raw string with a `decodeURIComponent`
|
|
69
|
+
* base64 layer. Kept read-only so existing compressed-at-rest values still
|
|
70
|
+
* decode after upgrading to the current byte-LZW codec. (That codec corrupted
|
|
71
|
+
* non-Latin1 input, so legacy payloads are Latin1 in practice.)
|
|
54
72
|
*/
|
|
55
|
-
private
|
|
73
|
+
private legacyLzDecompress;
|
|
74
|
+
private legacyDecodeFromBase64;
|
|
56
75
|
/**
|
|
57
76
|
* Get compression ratio
|
|
58
77
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compression.d.ts","sourceRoot":"","sources":["../../src/features/compression.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"compression.d.ts","sourceRoot":"","sources":["../../src/features/compression.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAA8B;gBAEhC,MAAM,GAAE,iBAAsB;IAQ1C;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG,OAAO,CAAC;IAkChE;;OAEG;IACG,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAmBzE;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,cAAc;IAcvD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAW;IAMzC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAU;IAE9C;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU;IAmClB;;;OAGG;IACH,OAAO,CAAC,YAAY;IA4CpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CACoC;IAErE,8EAA8E;IAC9E,OAAO,CAAC,aAAa;IAyBrB,oFAAoF;IACpF,OAAO,CAAC,aAAa;IA2BrB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IA8B1B,OAAO,CAAC,sBAAsB;IAiB9B;;OAEG;IACH,mBAAmB,CAAC,cAAc,EAAE,cAAc,GAAG,MAAM;IAI3D;;OAEG;IACH,oBAAoB,CAAC,cAAc,EAAE,cAAc,GAAG,MAAM;CAO7D"}
|
|
@@ -34,8 +34,12 @@ export class CompressionManager {
|
|
|
34
34
|
return data;
|
|
35
35
|
}
|
|
36
36
|
return {
|
|
37
|
+
// Tag the on-disk format so decompress() can pick the right decoder.
|
|
38
|
+
// Older releases wrote `algorithm: 'lz'` with a UTF-16-char codec that
|
|
39
|
+
// corrupted non-Latin1 data; the current byte-LZW format is tagged
|
|
40
|
+
// distinctly and old 'lz' payloads still read via the legacy path.
|
|
37
41
|
data: compressed,
|
|
38
|
-
algorithm:
|
|
42
|
+
algorithm: CompressionManager.FORMAT,
|
|
39
43
|
originalSize,
|
|
40
44
|
compressedSize,
|
|
41
45
|
};
|
|
@@ -52,7 +56,12 @@ export class CompressionManager {
|
|
|
52
56
|
if (!this.isCompressedData(compressedData)) {
|
|
53
57
|
return compressedData;
|
|
54
58
|
}
|
|
55
|
-
|
|
59
|
+
// Route by stored format tag: the current byte-LZW format vs. the legacy
|
|
60
|
+
// UTF-16-char codec written by <= 2.7.0 (algorithm 'lz'), so existing
|
|
61
|
+
// compressed-at-rest values keep decoding after upgrade.
|
|
62
|
+
const decompressed = compressedData.algorithm === CompressionManager.FORMAT
|
|
63
|
+
? this.lzDecompress(compressedData.data)
|
|
64
|
+
: this.legacyLzDecompress(compressedData.data);
|
|
56
65
|
return JSON.parse(decompressed);
|
|
57
66
|
}
|
|
58
67
|
catch (error) {
|
|
@@ -70,120 +79,198 @@ export class CompressionManager {
|
|
|
70
79
|
'originalSize' in data &&
|
|
71
80
|
'compressedSize' in data);
|
|
72
81
|
}
|
|
82
|
+
// Format tag stored in CompressedData.algorithm for the current byte-LZW
|
|
83
|
+
// codec. Payloads written by <= 2.7.0 are tagged 'lz' and decode via the
|
|
84
|
+
// legacy path; anything tagged FORMAT uses the current decoder.
|
|
85
|
+
static FORMAT = 'lz-u8';
|
|
86
|
+
// Freeze the LZW dictionary at 16 bits so every token serializes to exactly
|
|
87
|
+
// two bytes; both encoder and decoder stop growing the dictionary at the same
|
|
88
|
+
// point, staying in sync. 0xFFFF entries is ample for storage-sized values;
|
|
89
|
+
// beyond it, already-learned phrases still compress, just no new ones.
|
|
90
|
+
static LZW_MAX_CODE = 0xffff;
|
|
73
91
|
/**
|
|
74
|
-
* LZ
|
|
92
|
+
* LZ compression over UTF-8 bytes.
|
|
93
|
+
*
|
|
94
|
+
* The alphabet is bytes (0–255), so the literal-vs-dictionary-code
|
|
95
|
+
* discriminator (`< 256`) on decompress is correct for ALL input — including
|
|
96
|
+
* CJK / emoji / symbols whose code points are ≥ 256. (Operating on raw UTF-16
|
|
97
|
+
* chars previously misclassified any such literal as a dictionary code and
|
|
98
|
+
* silently corrupted the stored value, or threw on read.)
|
|
75
99
|
*/
|
|
76
100
|
lzCompress(uncompressed) {
|
|
77
|
-
|
|
101
|
+
const bytes = new TextEncoder().encode(uncompressed);
|
|
102
|
+
if (bytes.length === 0)
|
|
78
103
|
return '';
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
let phrase =
|
|
83
|
-
let
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
phrase
|
|
104
|
+
const dictionary = new Map();
|
|
105
|
+
let nextCode = 256;
|
|
106
|
+
const tokens = [];
|
|
107
|
+
let phrase = String.fromCharCode(bytes[0]); // byte-string (each char 0–255)
|
|
108
|
+
for (let i = 1; i < bytes.length; i++) {
|
|
109
|
+
const current = String.fromCharCode(bytes[i]);
|
|
110
|
+
const combined = phrase + current;
|
|
111
|
+
if (dictionary.has(combined)) {
|
|
112
|
+
phrase = combined;
|
|
88
113
|
}
|
|
89
114
|
else {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
115
|
+
// `phrase` is always either a single byte (literal) or an existing
|
|
116
|
+
// dictionary entry, so the lookup below is always defined.
|
|
117
|
+
tokens.push(phrase.length === 1 ? phrase.charCodeAt(0) : dictionary.get(phrase));
|
|
118
|
+
if (nextCode <= CompressionManager.LZW_MAX_CODE) {
|
|
119
|
+
dictionary.set(combined, nextCode++);
|
|
95
120
|
}
|
|
96
|
-
|
|
97
|
-
code++;
|
|
98
|
-
phrase = currentChar;
|
|
121
|
+
phrase = current;
|
|
99
122
|
}
|
|
100
123
|
}
|
|
101
|
-
|
|
102
|
-
|
|
124
|
+
tokens.push(phrase.length === 1 ? phrase.charCodeAt(0) : dictionary.get(phrase));
|
|
125
|
+
// Serialize each token as a 16-bit big-endian value, then base64.
|
|
126
|
+
const out = new Uint8Array(tokens.length * 2);
|
|
127
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
128
|
+
out[i * 2] = (tokens[i] >> 8) & 0xff;
|
|
129
|
+
out[i * 2 + 1] = tokens[i] & 0xff;
|
|
103
130
|
}
|
|
104
|
-
|
|
105
|
-
out.push(phrase);
|
|
106
|
-
}
|
|
107
|
-
return this.encodeToBase64(out.join(''));
|
|
131
|
+
return this.bytesToBase64(out);
|
|
108
132
|
}
|
|
109
133
|
/**
|
|
110
|
-
* LZ
|
|
134
|
+
* LZ decompression — inverse of {@link lzCompress}, reconstructing UTF-8 bytes
|
|
135
|
+
* then decoding them back to the original string.
|
|
111
136
|
*/
|
|
112
137
|
lzDecompress(compressed) {
|
|
113
|
-
if (compressed
|
|
138
|
+
if (!compressed)
|
|
114
139
|
return '';
|
|
115
|
-
|
|
140
|
+
const inBytes = this.base64ToBytes(compressed);
|
|
141
|
+
const tokenCount = inBytes.length >> 1;
|
|
142
|
+
if (tokenCount === 0)
|
|
116
143
|
return '';
|
|
117
|
-
const decoded = this.decodeFromBase64(compressed);
|
|
118
144
|
const dictionary = new Map();
|
|
119
|
-
let
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
for (let i = 1; i <
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
let nextCode = 256;
|
|
146
|
+
const readToken = (i) => (inBytes[i * 2] << 8) | inBytes[i * 2 + 1];
|
|
147
|
+
// The first token is always a literal byte (the dictionary starts empty).
|
|
148
|
+
let oldPhrase = String.fromCharCode(readToken(0));
|
|
149
|
+
const out = [oldPhrase];
|
|
150
|
+
for (let i = 1; i < tokenCount; i++) {
|
|
151
|
+
const code = readToken(i);
|
|
152
|
+
let phrase;
|
|
153
|
+
if (code < 256) {
|
|
154
|
+
phrase = String.fromCharCode(code);
|
|
155
|
+
}
|
|
156
|
+
else if (dictionary.has(code)) {
|
|
157
|
+
phrase = dictionary.get(code);
|
|
128
158
|
}
|
|
129
159
|
else {
|
|
130
|
-
|
|
160
|
+
// Classic LZW edge: the code equals the entry we are about to define.
|
|
161
|
+
phrase = oldPhrase + oldPhrase.charAt(0);
|
|
131
162
|
}
|
|
132
163
|
out.push(phrase);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
if (nextCode <= CompressionManager.LZW_MAX_CODE) {
|
|
165
|
+
dictionary.set(nextCode++, oldPhrase + phrase.charAt(0));
|
|
166
|
+
}
|
|
136
167
|
oldPhrase = phrase;
|
|
137
168
|
}
|
|
138
|
-
|
|
169
|
+
// `out` holds byte-strings; rebuild the byte sequence and UTF-8 decode it.
|
|
170
|
+
const joined = out.join('');
|
|
171
|
+
const bytes = new Uint8Array(joined.length);
|
|
172
|
+
for (let i = 0; i < joined.length; i++) {
|
|
173
|
+
bytes[i] = joined.charCodeAt(i) & 0xff;
|
|
174
|
+
}
|
|
175
|
+
return new TextDecoder().decode(bytes);
|
|
139
176
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
encodeToBase64(input) {
|
|
177
|
+
static B64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
178
|
+
/** Base64-encode raw bytes (btoa when available, else a pure-JS fallback). */
|
|
179
|
+
bytesToBase64(bytes) {
|
|
144
180
|
if (typeof btoa !== 'undefined') {
|
|
145
|
-
|
|
146
|
-
|
|
181
|
+
let binary = '';
|
|
182
|
+
const chunk = 0x8000;
|
|
183
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
184
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
185
|
+
}
|
|
186
|
+
return btoa(binary);
|
|
147
187
|
}
|
|
148
|
-
|
|
149
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
188
|
+
const chars = CompressionManager.B64_CHARS;
|
|
150
189
|
let result = '';
|
|
151
|
-
let i = 0;
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
const c = i < input.length ? input.charCodeAt(i++) : 0;
|
|
190
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
191
|
+
const a = bytes[i];
|
|
192
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
193
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
156
194
|
const bitmap = (a << 16) | (b << 8) | c;
|
|
157
195
|
result += chars.charAt((bitmap >> 18) & 63);
|
|
158
196
|
result += chars.charAt((bitmap >> 12) & 63);
|
|
159
|
-
result += i
|
|
160
|
-
result += i
|
|
197
|
+
result += i + 1 < bytes.length ? chars.charAt((bitmap >> 6) & 63) : '=';
|
|
198
|
+
result += i + 2 < bytes.length ? chars.charAt(bitmap & 63) : '=';
|
|
161
199
|
}
|
|
162
200
|
return result;
|
|
163
201
|
}
|
|
202
|
+
/** Decode a base64 string back to raw bytes (atob when available, else pure-JS). */
|
|
203
|
+
base64ToBytes(input) {
|
|
204
|
+
if (typeof atob !== 'undefined') {
|
|
205
|
+
const binary = atob(input);
|
|
206
|
+
const bytes = new Uint8Array(binary.length);
|
|
207
|
+
for (let i = 0; i < binary.length; i++) {
|
|
208
|
+
bytes[i] = binary.charCodeAt(i) & 0xff;
|
|
209
|
+
}
|
|
210
|
+
return bytes;
|
|
211
|
+
}
|
|
212
|
+
const chars = CompressionManager.B64_CHARS;
|
|
213
|
+
const clean = input.replace(/[^A-Za-z0-9+/]/g, '');
|
|
214
|
+
const bytes = [];
|
|
215
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
216
|
+
const e1 = chars.indexOf(clean.charAt(i));
|
|
217
|
+
const e2 = chars.indexOf(clean.charAt(i + 1));
|
|
218
|
+
const e3 = chars.indexOf(clean.charAt(i + 2));
|
|
219
|
+
const e4 = chars.indexOf(clean.charAt(i + 3));
|
|
220
|
+
const bitmap = (e1 << 18) | (Math.max(e2, 0) << 12) | (Math.max(e3, 0) << 6) | Math.max(e4, 0);
|
|
221
|
+
bytes.push((bitmap >> 16) & 255);
|
|
222
|
+
if (e3 !== -1)
|
|
223
|
+
bytes.push((bitmap >> 8) & 255);
|
|
224
|
+
if (e4 !== -1)
|
|
225
|
+
bytes.push(bitmap & 255);
|
|
226
|
+
}
|
|
227
|
+
return new Uint8Array(bytes);
|
|
228
|
+
}
|
|
164
229
|
/**
|
|
165
|
-
*
|
|
230
|
+
* Legacy decoder for payloads written by <= 2.7.0 (algorithm 'lz'): the
|
|
231
|
+
* original UTF-16-char LZW over the raw string with a `decodeURIComponent`
|
|
232
|
+
* base64 layer. Kept read-only so existing compressed-at-rest values still
|
|
233
|
+
* decode after upgrading to the current byte-LZW codec. (That codec corrupted
|
|
234
|
+
* non-Latin1 input, so legacy payloads are Latin1 in practice.)
|
|
166
235
|
*/
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
|
|
236
|
+
legacyLzDecompress(compressed) {
|
|
237
|
+
if (!compressed)
|
|
238
|
+
return '';
|
|
239
|
+
const decoded = typeof atob !== 'undefined'
|
|
240
|
+
? decodeURIComponent(escape(atob(compressed)))
|
|
241
|
+
: this.legacyDecodeFromBase64(compressed);
|
|
242
|
+
const dictionary = new Map();
|
|
243
|
+
let currentChar = decoded.charAt(0);
|
|
244
|
+
let oldPhrase = currentChar;
|
|
245
|
+
const out = [currentChar];
|
|
246
|
+
let code = 256;
|
|
247
|
+
for (let i = 1; i < decoded.length; i++) {
|
|
248
|
+
const currentCode = decoded.charCodeAt(i);
|
|
249
|
+
const phrase = currentCode < 256
|
|
250
|
+
? decoded.charAt(i)
|
|
251
|
+
: dictionary.get(currentCode) || oldPhrase + currentChar;
|
|
252
|
+
out.push(phrase);
|
|
253
|
+
currentChar = phrase.charAt(0);
|
|
254
|
+
dictionary.set(code, oldPhrase + currentChar);
|
|
255
|
+
code++;
|
|
256
|
+
oldPhrase = phrase;
|
|
171
257
|
}
|
|
172
|
-
|
|
173
|
-
|
|
258
|
+
return out.join('');
|
|
259
|
+
}
|
|
260
|
+
legacyDecodeFromBase64(input) {
|
|
261
|
+
const chars = `${CompressionManager.B64_CHARS}=`;
|
|
174
262
|
let result = '';
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4;
|
|
263
|
+
const clean = input.replace(/[^A-Za-z0-9+/]/g, '');
|
|
264
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
265
|
+
const e1 = chars.indexOf(clean.charAt(i));
|
|
266
|
+
const e2 = chars.indexOf(clean.charAt(i + 1));
|
|
267
|
+
const e3 = chars.indexOf(clean.charAt(i + 2));
|
|
268
|
+
const e4 = chars.indexOf(clean.charAt(i + 3));
|
|
269
|
+
const bitmap = (e1 << 18) | (e2 << 12) | (e3 << 6) | e4;
|
|
183
270
|
result += String.fromCharCode((bitmap >> 16) & 255);
|
|
184
|
-
if (
|
|
271
|
+
if (e3 !== 64 && e3 !== -1)
|
|
185
272
|
result += String.fromCharCode((bitmap >> 8) & 255);
|
|
186
|
-
if (
|
|
273
|
+
if (e4 !== 64 && e4 !== -1)
|
|
187
274
|
result += String.fromCharCode(bitmap & 255);
|
|
188
275
|
}
|
|
189
276
|
return result;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "../encryption.js";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,6 +10,7 @@ export interface EncryptionConfig {
|
|
|
10
10
|
keyLength?: 128 | 192 | 256;
|
|
11
11
|
iterations?: number;
|
|
12
12
|
saltLength?: number;
|
|
13
|
+
keyDerivation?: 'PBKDF2';
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Encrypted data structure
|
|
@@ -20,16 +21,37 @@ export interface EncryptedData {
|
|
|
20
21
|
iv: string;
|
|
21
22
|
algorithm: string;
|
|
22
23
|
iterations: number;
|
|
24
|
+
/**
|
|
25
|
+
* Base64 HMAC-SHA256 tag over (iv || ciphertext), present only for AES-CBC.
|
|
26
|
+
*
|
|
27
|
+
* AES-CBC provides confidentiality but NOT authentication, so without a MAC a
|
|
28
|
+
* CBC ciphertext is malleable and vulnerable to padding-oracle / bit-flip
|
|
29
|
+
* attacks. We therefore use Encrypt-then-MAC: a separate HMAC key is derived
|
|
30
|
+
* from the same password (domain-separated salt) and the tag is verified
|
|
31
|
+
* before decryption. AES-GCM is authenticated by construction and does not
|
|
32
|
+
* carry this field.
|
|
33
|
+
*/
|
|
34
|
+
mac?: string;
|
|
23
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Default PBKDF2 iteration count. Raised to 600,000 to match current OWASP
|
|
38
|
+
* guidance for PBKDF2-HMAC-SHA256. The per-record `iterations` value is stored
|
|
39
|
+
* in {@link EncryptedData} and used on decrypt, so raising this default never
|
|
40
|
+
* breaks data written with an older, lower count.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_PBKDF2_ITERATIONS = 600000;
|
|
24
43
|
/**
|
|
25
44
|
* Encryption manager using Web Crypto API
|
|
26
45
|
*/
|
|
27
46
|
export declare class EncryptionManager {
|
|
28
47
|
private config;
|
|
29
48
|
private keyCache;
|
|
49
|
+
private macKeyCache;
|
|
30
50
|
constructor(config?: EncryptionConfig);
|
|
31
51
|
/**
|
|
32
|
-
* Check if encryption is available
|
|
52
|
+
* Check if encryption is available. Uses the Web Crypto API via `globalThis`
|
|
53
|
+
* so it works in browsers, Web Workers, and modern Node.js / SSR runtimes
|
|
54
|
+
* (Node 20+ exposes `globalThis.crypto.subtle`) — not just the browser.
|
|
33
55
|
*/
|
|
34
56
|
isAvailable(): boolean;
|
|
35
57
|
/**
|
|
@@ -44,6 +66,32 @@ export declare class EncryptionManager {
|
|
|
44
66
|
* Derive encryption key from password
|
|
45
67
|
*/
|
|
46
68
|
private deriveKey;
|
|
69
|
+
/**
|
|
70
|
+
* Derive a separate HMAC-SHA256 key for Encrypt-then-MAC on AES-CBC.
|
|
71
|
+
*
|
|
72
|
+
* The HMAC key must be cryptographically independent of the AES key, so the
|
|
73
|
+
* PBKDF2 salt is domain-separated with a trailing marker byte. The MAC key is
|
|
74
|
+
* non-extractable and limited to sign/verify.
|
|
75
|
+
*/
|
|
76
|
+
private deriveMacKey;
|
|
77
|
+
/**
|
|
78
|
+
* Import a password string as PBKDF2 key material.
|
|
79
|
+
*/
|
|
80
|
+
private importPasswordKeyMaterial;
|
|
81
|
+
/**
|
|
82
|
+
* Build an opaque cache key by hashing the password together with the public
|
|
83
|
+
* derivation parameters. Returns a base64 SHA-256 digest so the raw password
|
|
84
|
+
* is never used as (or recoverable from) the Map key.
|
|
85
|
+
*/
|
|
86
|
+
private hashCacheKey;
|
|
87
|
+
/**
|
|
88
|
+
* Copy a (possibly offset) typed array into a standalone ArrayBuffer.
|
|
89
|
+
*/
|
|
90
|
+
private toArrayBuffer;
|
|
91
|
+
/**
|
|
92
|
+
* Concatenate two buffers into one ArrayBuffer.
|
|
93
|
+
*/
|
|
94
|
+
private concatBuffers;
|
|
47
95
|
/**
|
|
48
96
|
* Convert ArrayBuffer to base64
|
|
49
97
|
*/
|
|
@@ -57,7 +105,12 @@ export declare class EncryptionManager {
|
|
|
57
105
|
*/
|
|
58
106
|
clearCache(): void;
|
|
59
107
|
/**
|
|
60
|
-
* Generate a secure random password
|
|
108
|
+
* Generate a secure random password.
|
|
109
|
+
*
|
|
110
|
+
* Uses rejection sampling so every character in the alphabet is equally
|
|
111
|
+
* likely. Naive `randomByte % alphabetLength` is biased whenever 256 is not a
|
|
112
|
+
* multiple of the alphabet length (here 89), making some characters more
|
|
113
|
+
* probable and slightly reducing entropy.
|
|
61
114
|
*/
|
|
62
115
|
generatePassword(length?: number): string;
|
|
63
116
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/features/encryption.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/features/encryption.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,QAAQ,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAEhD;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,WAAW,CAAqC;gBAE5C,MAAM,GAAE,gBAAqB;IAczC;;;;OAIG;IACH,WAAW,IAAI,OAAO;IAKtB;;OAEG;IACG,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAsDtE;;OAEG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IA6DtF;;OAEG;YACW,SAAS;IAuCvB;;;;;;OAMG;YACW,YAAY;IAyC1B;;OAEG;YACW,yBAAyB;IASvC;;;;OAIG;YACW,YAAY;IAM1B;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;;;;OAOG;IACH,gBAAgB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAsB7C;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAU1C"}
|