• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

javascript-obfuscator / javascript-obfuscator / 19907815758

03 Dec 2025 08:27PM UTC coverage: 97.319%. Remained the same
19907815758

push

github

sanex3339
Adjust precommit hook

1770 of 1891 branches covered (93.6%)

Branch coverage included in aggregate %.

5671 of 5755 relevant lines covered (98.54%)

34102965.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.08
/src/storages/string-array-transformers/StringArrayStorage.ts
1
import { inject, injectable, postConstruct } from 'inversify';
6✔
2
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
6✔
3

4
import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
5
import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
6

7
import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
8
import { ICryptUtilsStringArray } from '../../interfaces/utils/ICryptUtilsStringArray';
9
import { IEncodedValue } from '../../interfaces/IEncodedValue';
10
import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
11
import { IOptions } from '../../interfaces/options/IOptions';
12
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
13
import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
14
import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-transformers/IStringArrayStorageItem';
15

16
import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
6✔
17

18
import { MapStorage } from '../MapStorage';
6✔
19

20
@injectable()
21
export class StringArrayStorage
6✔
22
    extends MapStorage<`${string}-${TStringArrayEncoding}`, IStringArrayStorageItemData>
23
    implements IStringArrayStorage
24
{
25
    /**
26
     * @type {number}
27
     */
28
    private static readonly minimumRotationAmount: number = 100;
6✔
29

30
    /**
31
     * @type {number}
32
     */
33
    private static readonly maximumRotationAmount: number = 500;
6✔
34

35
    /**
36
     * @type {number}
37
     */
38
    private static readonly minimumIndexShiftAmount: number = 100;
6✔
39

40
    /**
41
     * @type {number}
42
     */
43
    private static readonly maximumIndexShiftAmount: number = 500;
6✔
44

45
    /**
46
     * @type {number}
47
     */
48
    private static readonly rc4KeyLength: number = 4;
6✔
49

50
    /**
51
     * @type {number}
52
     */
53
    private static readonly rc4KeysCount: number = 50;
6✔
54

55
    /**
56
     * @type {number}
57
     */
58
    private static readonly stringArrayFunctionNameLength: number = 4;
6✔
59

60
    /**
61
     * @type {IArrayUtils}
62
     */
63
    private readonly arrayUtils: IArrayUtils;
64

65
    /**
66
     * @type {ICryptUtilsStringArray}
67
     */
68
    private readonly cryptUtilsStringArray: ICryptUtilsStringArray;
69

70
    /**
71
     * @type {IIdentifierNamesGenerator}
72
     */
73
    private readonly identifierNamesGenerator: IIdentifierNamesGenerator;
74

75
    /**
76
     * @type {string[]}
77
     */
78
    private readonly rc4Keys: string[];
79

80
    /**
81
     * @type {Map<string, string[]>}
82
     */
83
    private readonly rc4EncodedValuesSourcesCache: Map<string, string[]> = new Map();
257,874✔
84

85
    /**
86
     * @type {number}
87
     */
88
    private indexShiftAmount: number = 0;
257,874✔
89

90
    /**
91
     * @type {number}
92
     */
93
    private rotationAmount: number = 0;
257,874✔
94

95
    /**
96
     * @type {string}
97
     */
98
    private stringArrayStorageName!: string;
99

100
    /**
101
     * @type {Map<TStringArrayEncoding | null, string>}
102
     */
103
    private readonly stringArrayStorageCallsWrapperNamesMap: Map<TStringArrayEncoding | null, string> = new Map();
257,874✔
104

105
    /**
106
     * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
107
     * @param {IArrayUtils} arrayUtils
108
     * @param {IRandomGenerator} randomGenerator
109
     * @param {IOptions} options
110
     * @param {ICryptUtilsStringArray} cryptUtilsStringArray
111
     */
112
    public constructor(
113
        @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
114
        identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
115
        @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
116
        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
117
        @inject(ServiceIdentifiers.IOptions) options: IOptions,
118
        @inject(ServiceIdentifiers.ICryptUtilsStringArray) cryptUtilsStringArray: ICryptUtilsStringArray
119
    ) {
120
        super(randomGenerator, options);
257,874✔
121

122
        this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
257,874✔
123
        this.arrayUtils = arrayUtils;
257,874✔
124
        this.cryptUtilsStringArray = cryptUtilsStringArray;
257,874✔
125

126
        this.rc4Keys = this.randomGenerator.getRandomGenerator().n(
257,874✔
127
            () =>
128
                this.randomGenerator.getRandomGenerator().string({
12,893,700✔
129
                    length: StringArrayStorage.rc4KeyLength
130
                }),
131
            StringArrayStorage.rc4KeysCount
132
        );
133
    }
134

135
    @postConstruct()
136
    public override initialize(): void {
6✔
137
        super.initialize();
257,886✔
138

139
        this.indexShiftAmount = this.options.stringArrayIndexShift
257,886✔
140
            ? this.randomGenerator.getRandomInteger(
257,886✔
141
                  StringArrayStorage.minimumIndexShiftAmount,
142
                  StringArrayStorage.maximumIndexShiftAmount
143
              )
144
            : 0;
145
        this.rotationAmount = this.options.stringArrayRotate
257,886✔
146
            ? this.randomGenerator.getRandomInteger(
257,886✔
147
                  StringArrayStorage.minimumRotationAmount,
148
                  StringArrayStorage.maximumRotationAmount
149
              )
150
            : 0;
151
    }
152

153
    /**
154
     * @param {string} value
155
     */
156
    public override get(value: string): IStringArrayStorageItemData {
157
        return this.getOrSetIfDoesNotExist(value);
8,812,940✔
158
    }
159

160
    /**
161
     * @returns {number}
162
     */
163
    public getIndexShiftAmount(): number {
164
        return this.indexShiftAmount;
8,661,062✔
165
    }
166

167
    /**
168
     * @returns {number}
169
     */
170
    public getRotationAmount(): number {
171
        return this.rotationAmount;
×
172
    }
173

174
    /**
175
     * @returns {string}
176
     */
177
    public getStorageName(): string {
178
        return this.getStorageId();
95,466✔
179
    }
180

181
    /**
182
     * @returns {string}
183
     */
184
    public override getStorageId(): string {
185
        if (!this.stringArrayStorageName) {
95,466✔
186
            this.stringArrayStorageName = this.identifierNamesGenerator.generateForGlobalScope(
77,394✔
187
                StringArrayStorage.stringArrayFunctionNameLength
188
            );
189
        }
190

191
        return this.stringArrayStorageName;
95,466✔
192
    }
193

194
    /**
195
     * @param {TStringArrayEncoding | null} stringArrayEncoding
196
     * @returns {IStringArrayCallsWrapperNames}
197
     */
198
    public getStorageCallsWrapperName(stringArrayEncoding: TStringArrayEncoding | null): string {
199
        const storageCallsWrapperName: string | null =
200
            this.stringArrayStorageCallsWrapperNamesMap.get(stringArrayEncoding) ?? null;
2,076,897✔
201

202
        if (storageCallsWrapperName) {
2,076,897✔
203
            return storageCallsWrapperName;
1,990,725✔
204
        }
205

206
        const newStorageCallsWrapperName: string = this.identifierNamesGenerator.generateForGlobalScope(
86,172✔
207
            StringArrayStorage.stringArrayFunctionNameLength
208
        );
209

210
        this.stringArrayStorageCallsWrapperNamesMap.set(stringArrayEncoding, newStorageCallsWrapperName);
86,172✔
211

212
        return newStorageCallsWrapperName;
86,172✔
213
    }
214

215
    public rotateStorage(): void {
216
        if (!this.getLength()) {
18,162✔
217
            return;
90✔
218
        }
219

220
        this.storage = new Map(this.arrayUtils.rotate(Array.from(this.storage.entries()), this.rotationAmount));
18,072✔
221
    }
222

223
    public shuffleStorage(): void {
224
        this.storage = new Map(
18,030✔
225
            this.arrayUtils
226
                .shuffle(Array.from(this.storage.entries()))
227
                .map<[`${string}-${TStringArrayEncoding}`, IStringArrayStorageItemData]>(
228
                    ([value, stringArrayStorageItemData], index: number) => {
229
                        stringArrayStorageItemData.index = index;
3,092,674✔
230

231
                        return [value, stringArrayStorageItemData];
3,092,674✔
232
                    }
233
                )
234
                .sort(
235
                    (
236
                        [, stringArrayStorageItemDataA]: [string, IStringArrayStorageItemData],
237
                        [, stringArrayStorageItemDataB]: [string, IStringArrayStorageItemData]
238
                    ) => stringArrayStorageItemDataA.index - stringArrayStorageItemDataB.index
3,074,728✔
239
                )
240
        );
241
    }
242

243
    /**
244
     * @param {string} value
245
     * @returns {IStringArrayStorageItemData}
246
     */
247
    private getOrSetIfDoesNotExist(value: string): IStringArrayStorageItemData {
248
        const { encodedValue, encoding, decodeKey }: IEncodedValue = this.getEncodedValue(value);
8,812,940✔
249

250
        const cacheKey: `${string}-${TStringArrayEncoding}` = `${encodedValue}-${encoding}`;
8,812,940✔
251
        const storedStringArrayStorageItemData: IStringArrayStorageItemData | undefined = this.storage.get(cacheKey);
8,812,940✔
252

253
        if (storedStringArrayStorageItemData) {
8,812,940✔
254
            return storedStringArrayStorageItemData;
5,260,539✔
255
        }
256

257
        const stringArrayStorageItemData: IStringArrayStorageItemData = {
3,552,401✔
258
            encodedValue,
259
            encoding,
260
            decodeKey,
261
            value,
262
            index: this.getLength()
263
        };
264

265
        this.storage.set(cacheKey, stringArrayStorageItemData);
3,552,401✔
266

267
        return stringArrayStorageItemData;
3,552,401✔
268
    }
269

270
    /**
271
     * @param {string} value
272
     * @returns {IEncodedValue}
273
     */
274
    private getEncodedValue(value: string): IEncodedValue {
275
        const encoding: TStringArrayEncoding | null = this.options.stringArrayEncoding.length
8,817,456✔
276
            ? this.randomGenerator.getRandomGenerator().pickone(this.options.stringArrayEncoding)
8,817,456!
277
            : null;
278

279
        if (!encoding) {
8,817,456!
280
            throw new Error('`stringArrayEncoding` option array is empty');
×
281
        }
282

283
        switch (encoding) {
8,817,456✔
284
            /**
285
             * For rc4 there is a possible chance of a collision between encoded values that were received from
286
             * different source values with different keys
287
             *
288
             * For example:
289
             * source value | key  | encoded value
290
             * _15          | CRDL | w74TGA==
291
             * _12          | q9mB | w74TGA==
292
             *
293
             * Issue: https://github.com/javascript-obfuscator/javascript-obfuscator/issues/538
294
             *
295
             * As a fix that keeps key size of 4 character, the simple brute-force solution is using:
296
             * if collision will happen, just try to encode value again
297
             */
298
            case StringArrayEncoding.Rc4: {
8,817,456✔
299
                const decodeKey: string = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys);
2,650,586✔
300
                const encodedValue: string = this.cryptUtilsStringArray.btoa(
2,650,586✔
301
                    this.cryptUtilsStringArray.rc4(value, decodeKey)
302
                );
303

304
                const encodedValueSources: string[] = this.rc4EncodedValuesSourcesCache.get(encodedValue) ?? [];
2,650,586✔
305
                let encodedValueSourcesLength: number = encodedValueSources.length;
2,650,586✔
306

307
                const shouldAddValueToSourcesCache: boolean =
308
                    !encodedValueSourcesLength || !encodedValueSources.includes(value);
2,650,586✔
309

310
                if (shouldAddValueToSourcesCache) {
2,650,586✔
311
                    encodedValueSources.push(value);
1,725,443✔
312
                    encodedValueSourcesLength++;
1,725,443✔
313
                }
314

315
                this.rc4EncodedValuesSourcesCache.set(encodedValue, encodedValueSources);
2,650,586✔
316

317
                if (encodedValueSourcesLength > 1) {
2,650,586✔
318
                    return this.getEncodedValue(value);
4,516✔
319
                }
320

321
                return { encodedValue, encoding, decodeKey };
2,646,070✔
322
            }
323

324
            case StringArrayEncoding.Base64: {
325
                const decodeKey: null = null;
2,615,389✔
326
                const encodedValue: string = this.cryptUtilsStringArray.btoa(value);
2,615,389✔
327

328
                return { encodedValue, encoding, decodeKey };
2,615,389✔
329
            }
330

331
            default: {
332
                const decodeKey: null = null;
3,551,481✔
333
                const encodedValue: string = value;
3,551,481✔
334

335
                return { encodedValue, encoding, decodeKey };
3,551,481✔
336
            }
337
        }
338
    }
339
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc