jscrambler-metro-plugin 9.0.4 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -30
- package/lib/constants.js +15 -3
- package/lib/index.js +236 -76
- package/lib/utils.js +62 -7
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# 
|
|
2
|
-
# Jscrambler Code Integrity for React-Native
|
|
2
|
+
# Jscrambler Code Integrity for React-Native and Vega Os
|
|
3
3
|
|
|
4
4
|
Jscrambler [Code Integrity](https://jscrambler.com/code-integrity) is a JavaScript protection technology for Web and Mobile Applications. Its main purpose is to enable JavaScript applications to become self-defensive and resilient to tampering and reverse engineering.
|
|
5
5
|
|
|
@@ -19,44 +19,75 @@ Please make sure you install the right version, otherwise some functionalities m
|
|
|
19
19
|
|
|
20
20
|
# Usage
|
|
21
21
|
|
|
22
|
-
This metro plugin protects your
|
|
22
|
+
This metro plugin protects your [React Native](https://reactnative.dev/) and [Vega](https://developer.amazon.com/apps-and-games/vega) bundle using Jscrambler.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
First install the **jscrambler-metro-plugin** as a development dependency
|
|
25
25
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
const jscramblerMetroPlugin = require('jscrambler-metro-plugin')(
|
|
29
|
-
/* optional */
|
|
30
|
-
{
|
|
31
|
-
enable: true,
|
|
32
|
-
enabledHermes: true, // true by default. If you are not using Hermes engine, set to false
|
|
33
|
-
ignoreFile: resolve(__dirname, '.jscramblerignore'),
|
|
34
|
-
params: [
|
|
35
|
-
{
|
|
36
|
-
name: 'selfDefending',
|
|
37
|
-
options: {
|
|
38
|
-
threshold: 1
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
module.exports = jscramblerMetroPlugin;
|
|
26
|
+
```shell
|
|
27
|
+
npm install -D jscrambler-metro.plugin
|
|
46
28
|
```
|
|
47
29
|
|
|
48
|
-
|
|
49
|
-
the usual `.jscramblerrc` file.
|
|
30
|
+
Then, set up the plugin in your `metro.config.js` by adding the following code:
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
32
|
+
```js
|
|
33
|
+
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
|
34
|
+
const jscramblerMetroPlugin = require('jscrambler-metro-plugin')();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Metro configuration
|
|
38
|
+
* https://reactnative.dev/docs/metro
|
|
39
|
+
*
|
|
40
|
+
* @type {import('@react-native/metro-config').MetroConfig}
|
|
41
|
+
*/
|
|
42
|
+
const config = {
|
|
43
|
+
...jscramblerMetroPlugin
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Finally, create a `.jscramblerrc` file with your Jscrambler configuration downloaded from Code Integrity Application Dashboard.
|
|
53
50
|
|
|
54
51
|
By default, Jscrambler protection is **ignored** when bundle mode is set for **Development**. You can override this behavior by setting env variable `JSCRAMBLER_METRO_DEV=true`
|
|
55
52
|
|
|
56
53
|
In order to activate source map generation effectively, you will need to enable source maps both in the Jscrambler configuration file, by adding the following parameter to said file:
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
...
|
|
58
|
+
"sourceMaps": true,
|
|
59
|
+
...
|
|
60
|
+
}
|
|
61
|
+
```
|
|
61
62
|
|
|
62
63
|
and in the [React Native app](https://reactnative.dev/docs/debugging-release-builds?platform=android#enabling-source-maps).
|
|
64
|
+
|
|
65
|
+
## Plugin Options
|
|
66
|
+
|
|
67
|
+
| Option | Type | Default | Description |
|
|
68
|
+
| --- | --- | --- | --- |
|
|
69
|
+
| `enable` | `boolean` | `true` | Enables Jscrambler protection. Set to `false` to skip protection. |
|
|
70
|
+
| `enabledHermes` | `boolean` | `true` | Enables Hermes-specific compatibility handling. Set to `false` when the app does not use Hermes. |
|
|
71
|
+
| `ignoreFile` | `string` | `.jscramblerignore` in the project root | Path to the ignore file used to exclude files from protection. |
|
|
72
|
+
| `params` | `Array<object>` | Value from `.jscramblerrc` | Overrides the `params` transformation list from `.jscramblerrc`, useful for testing. |
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const jscramblerMetroPlugin = require('jscrambler-metro-plugin')(
|
|
76
|
+
/* OPTIONAL */
|
|
77
|
+
{
|
|
78
|
+
enable: true,
|
|
79
|
+
enabledHermes: true,
|
|
80
|
+
ignoreFile: resolve(__dirname, '.jscramblerignore'),
|
|
81
|
+
// overrides the params on `.jscramblerrc` file. Usefull for testing purposes.
|
|
82
|
+
params: [
|
|
83
|
+
{
|
|
84
|
+
name: "stringConcealing",
|
|
85
|
+
options: {
|
|
86
|
+
freq: 1,
|
|
87
|
+
max: -1
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
```
|
package/lib/constants.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** commands used to build the bundle
|
|
4
|
+
* - bundle: react native standard bundle command for android/iOS
|
|
5
|
+
* - export:embed: react native export command for expo builds
|
|
6
|
+
* - build-kepler: react native legacy bundle command for VegaOS
|
|
7
|
+
* - build-vega: react native current bundle command for VegaOS
|
|
8
|
+
*/
|
|
9
|
+
const EXPO_BUNDLE_CMDS = ['export:embed'];
|
|
10
|
+
const VEGA_BUNDLE_CMDS = ['build-kepler', 'build-vega'];
|
|
11
|
+
const BUNDLE_CMDS = ['bundle', ...VEGA_BUNDLE_CMDS, ...EXPO_BUNDLE_CMDS ];
|
|
4
12
|
const BUNDLE_OUTPUT_CLI_ARG = '--bundle-output';
|
|
5
13
|
const BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG = '--sourcemap-output';
|
|
6
14
|
const BUNDLE_DEV_CLI_ARG = '--dev';
|
|
15
|
+
const BUNDLE_EAGER_CLI_ARG = '--eager';
|
|
7
16
|
// path.join so it supports both linux and windows fs
|
|
8
17
|
const INIT_CORE_MODULE = path.join(process.platform === 'win32' ? '/' : '', 'node_modules', 'react-native', 'Libraries', 'Core', 'InitializeCore.js');
|
|
9
18
|
const JSCRAMBLER_CLIENT_ID = 6;
|
|
@@ -35,10 +44,13 @@ const JSCRAMBLER_HERMES_ADD_SHOW_SOURCE_DIRECTIVE = [
|
|
|
35
44
|
];
|
|
36
45
|
|
|
37
46
|
module.exports = {
|
|
38
|
-
|
|
47
|
+
BUNDLE_CMDS,
|
|
48
|
+
EXPO_BUNDLE_CMDS,
|
|
49
|
+
VEGA_BUNDLE_CMDS,
|
|
39
50
|
BUNDLE_OUTPUT_CLI_ARG,
|
|
40
51
|
BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG,
|
|
41
52
|
BUNDLE_DEV_CLI_ARG,
|
|
53
|
+
BUNDLE_EAGER_CLI_ARG,
|
|
42
54
|
INIT_CORE_MODULE,
|
|
43
55
|
JSCRAMBLER_CLIENT_ID,
|
|
44
56
|
JSCRAMBLER_IGNORE,
|
|
@@ -58,5 +70,5 @@ module.exports = {
|
|
|
58
70
|
JSCRAMBLER_HERMES_ADD_SHOW_SOURCE_DIRECTIVE,
|
|
59
71
|
JSCRAMBLER_ANTI_TAMPERING_MODE_SKL,
|
|
60
72
|
HERMES_SHOW_SOURCE_DIRECTIVE,
|
|
61
|
-
JSCRAMBLER_EXTS
|
|
73
|
+
JSCRAMBLER_EXTS,
|
|
62
74
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const {copy, emptyDir, mkdirp, readFile, writeFile} = require('fs-extra');
|
|
2
|
+
const { getDefaultConfig } = require('@react-native/metro-config');
|
|
3
|
+
const { getDefaultConfig: getDefaultExpoConfig } = require('expo/metro-config');
|
|
2
4
|
const jscrambler = require('jscrambler').default;
|
|
3
5
|
const fs = require('fs');
|
|
4
6
|
const path = require('path');
|
|
@@ -34,7 +36,10 @@ const {
|
|
|
34
36
|
handleAntiTampering,
|
|
35
37
|
addHermesShowSourceDirective,
|
|
36
38
|
handleHermesIncompatibilities,
|
|
37
|
-
wrapCodeWithTags
|
|
39
|
+
wrapCodeWithTags,
|
|
40
|
+
isVegaBuild,
|
|
41
|
+
isExpoBuild,
|
|
42
|
+
resolveMetroOutputBundle
|
|
38
43
|
} = require('./utils');
|
|
39
44
|
|
|
40
45
|
const debug = !!process.env.DEBUG;
|
|
@@ -277,12 +282,172 @@ function validateModule(modulePath, config, projectRoot) {
|
|
|
277
282
|
}
|
|
278
283
|
}
|
|
279
284
|
|
|
285
|
+
let calledByMetro = false;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Validates that Metro invoked the Jscrambler serializer integration.
|
|
289
|
+
*
|
|
290
|
+
* If Metro never calls the serializer hook, the plugin was not applied through
|
|
291
|
+
* metro.config.js and the bundle cannot be obfuscated reliably.
|
|
292
|
+
* @returns {void}
|
|
293
|
+
*/
|
|
294
|
+
function validateIfJscramblerWasApplied() {
|
|
295
|
+
if (!calledByMetro) {
|
|
296
|
+
console.error(
|
|
297
|
+
'*jscrambler-metro-plugin* was not properly configured on metro.config.js file. Please verify our documentation in https://docs.jscrambler.com/code-integrity/frameworks-and-libraries/react-native/integration.',
|
|
298
|
+
);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Configures the Metro integration for Vega OS builds.
|
|
305
|
+
*
|
|
306
|
+
* Vega OS overrides Metro's processModuleFilter, so this patches Metro's bundle
|
|
307
|
+
* save path to run Jscrambler after Metro writes the output and uses
|
|
308
|
+
* getPolyfills to wrap the final module filter.
|
|
309
|
+
* @param {object} options
|
|
310
|
+
* @param {function(*): boolean} options.applyJscramblerSerializerToModule Applies
|
|
311
|
+
* Jscrambler tagging and filtering to a Metro module.
|
|
312
|
+
* @param {function(string, string=): Promise<void>} options.runObfuscation Runs
|
|
313
|
+
* Jscrambler against the generated bundle and optional source map.
|
|
314
|
+
* @param {string} options.projectRoot Project root used to resolve Metro.
|
|
315
|
+
* @returns {{serializer: {getPolyfills(): string[]}}} Vega OS Metro config.
|
|
316
|
+
*/
|
|
317
|
+
function setupViaSaveHook({
|
|
318
|
+
applyJscramblerSerializerToModule,
|
|
319
|
+
runObfuscation,
|
|
320
|
+
projectRoot,
|
|
321
|
+
}) {
|
|
322
|
+
const metroOutput = resolveMetroOutputBundle(projectRoot);
|
|
323
|
+
if (!metroOutput) {
|
|
324
|
+
console.error(
|
|
325
|
+
`Jscrambler could not hook Metro bundle output. Please contact Jscrambler support team at support@jscrambler.com`,
|
|
326
|
+
);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const originalSave = metroOutput.save.bind(metroOutput);
|
|
331
|
+
|
|
332
|
+
metroOutput.save = async function jscramblerMetroSave(...opts) {
|
|
333
|
+
const [, args] = opts;
|
|
334
|
+
if (typeof args !== 'object' || args === null || !args.bundleOutput) {
|
|
335
|
+
console.error(
|
|
336
|
+
`Jscrambler output bundle could not be found. Please contact Jscrambler support at support@jscrambler.com`,
|
|
337
|
+
);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const result = await originalSave(...opts);
|
|
342
|
+
if (args && args.bundleOutput) {
|
|
343
|
+
try {
|
|
344
|
+
debug &&
|
|
345
|
+
console.log(
|
|
346
|
+
`debug Jscrambler obfuscating bundle at ${args.bundleOutput}`,
|
|
347
|
+
);
|
|
348
|
+
console.log(
|
|
349
|
+
`info Jscrambler ${isVegaBuild() ? 'VegaOS' : 'Expo'} application`,
|
|
350
|
+
);
|
|
351
|
+
await runObfuscation(args.bundleOutput, args.sourcemapOutput);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.error(err);
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
if (isExpoBuild()) {
|
|
361
|
+
const getDefaultprocessModuleFilter = getDefaultExpoConfig(projectRoot)?.serializer?.processModuleFilter;
|
|
362
|
+
return {
|
|
363
|
+
serializer: {
|
|
364
|
+
processModuleFilter(_module) {
|
|
365
|
+
const allow = getDefaultprocessModuleFilter(_module);
|
|
366
|
+
if (allow) {
|
|
367
|
+
applyJscramblerSerializerToModule(_module);
|
|
368
|
+
}
|
|
369
|
+
return allow;
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const getPolyfills = getDefaultConfig(projectRoot)?.serializer?.getPolyfills;
|
|
376
|
+
const metroPollyfils =
|
|
377
|
+
typeof getPolyfills === 'function' ? getPolyfills() : [];
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
serializer: {
|
|
381
|
+
getPolyfills() {
|
|
382
|
+
// wrap processModuleFilter to apply Jscrambler module filtering
|
|
383
|
+
// due to Amazon overriding processModuleFilter in VegaOS
|
|
384
|
+
const originalProcessModuleFilter = this.processModuleFilter;
|
|
385
|
+
this.processModuleFilter = (_module) => {
|
|
386
|
+
const allow = originalProcessModuleFilter(_module);
|
|
387
|
+
if (allow) {
|
|
388
|
+
applyJscramblerSerializerToModule(_module);
|
|
389
|
+
}
|
|
390
|
+
return allow;
|
|
391
|
+
};
|
|
392
|
+
return metroPollyfils;
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Configures the Metro integration for React Native builds.
|
|
400
|
+
*
|
|
401
|
+
* Registers a beforeExit hook that obfuscates the bundle produced by Metro and
|
|
402
|
+
* returns a serializer processModuleFilter that tags user modules while Metro
|
|
403
|
+
* serializes them.
|
|
404
|
+
* @param {object} options
|
|
405
|
+
* @param {function(*): boolean} options.applyJscramblerSerializerToModule Applies
|
|
406
|
+
* Jscrambler tagging and filtering to a Metro module.
|
|
407
|
+
* @param {function(string, string=): Promise<void>} options.runObfuscation Runs
|
|
408
|
+
* Jscrambler against the generated bundle and optional source map.
|
|
409
|
+
* @returns {{serializer: {processModuleFilter(*): boolean}}} React Native Metro config.
|
|
410
|
+
*/
|
|
411
|
+
function setupViaBeforeExitEvent({
|
|
412
|
+
applyJscramblerSerializerToModule,
|
|
413
|
+
runObfuscation,
|
|
414
|
+
}) {
|
|
415
|
+
process.on('beforeExit', async (exitCode) => {
|
|
416
|
+
try {
|
|
417
|
+
const bundlePaths = getBundlePath();
|
|
418
|
+
await runObfuscation(
|
|
419
|
+
bundlePaths.bundlePath,
|
|
420
|
+
bundlePaths.bundleSourceMapPath,
|
|
421
|
+
);
|
|
422
|
+
process.exit(typeof exitCode === 'number' ? exitCode : 0);
|
|
423
|
+
} catch (err) {
|
|
424
|
+
console.error(err);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Select user files ONLY (no vendor) to be obfuscated. That code should be tagged with
|
|
431
|
+
* {@JSCRAMBLER_BEG_ANNOTATION} and {@JSCRAMBLER_END_ANNOTATION}.
|
|
432
|
+
* Also gather metro source-maps in case of instrumentation process.
|
|
433
|
+
* @param {{output: Array<*>, path: string, getSource: function():Buffer}} _module
|
|
434
|
+
* @returns {boolean}
|
|
435
|
+
*/
|
|
436
|
+
return {
|
|
437
|
+
serializer: {
|
|
438
|
+
processModuleFilter(_module) {
|
|
439
|
+
return applyJscramblerSerializerToModule(_module);
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
280
445
|
/**
|
|
281
446
|
* Add serialize.processModuleFilter option to metro and attach listener to beforeExit event.
|
|
282
447
|
* *config.fileSrc* and *config.filesDest* will be ignored.
|
|
283
448
|
* @param {{enable: boolean, enabledHermes: boolean }} _config
|
|
284
449
|
* @param {string} [projectRoot=process.cwd()]
|
|
285
|
-
* @returns {{serializer: {processModuleFilter(*): boolean}}}
|
|
450
|
+
* @returns {{serializer: {processModuleFilter?(*): boolean, getPolyfills?(*): string[]}}}
|
|
286
451
|
*/
|
|
287
452
|
module.exports = function (_config = {}, projectRoot = process.cwd()) {
|
|
288
453
|
const skipReason = skipObfuscation(_config);
|
|
@@ -291,9 +456,6 @@ module.exports = function (_config = {}, projectRoot = process.cwd()) {
|
|
|
291
456
|
return {};
|
|
292
457
|
}
|
|
293
458
|
|
|
294
|
-
const bundlePath = getBundlePath();
|
|
295
|
-
// make sure jscrambler-metro-plugin is properly configure on metro bundler
|
|
296
|
-
let calledByMetro = false;
|
|
297
459
|
const fileNames = new Set();
|
|
298
460
|
const sourceMapFiles = [];
|
|
299
461
|
const config = Object.assign(
|
|
@@ -313,84 +475,82 @@ module.exports = function (_config = {}, projectRoot = process.cwd()) {
|
|
|
313
475
|
console.warn('warning: Jscrambler recommends you to declare your transformations list on the configuration file.')
|
|
314
476
|
}
|
|
315
477
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (!calledByMetro) {
|
|
319
|
-
throw new Error('*jscrambler-metro-plugin* was not properly configured on metro.config.js file. Please verify our documentation in https://docs.jscrambler.com/code-integrity/frameworks-and-libraries/react-native/integration.');
|
|
320
|
-
}
|
|
478
|
+
async function runObfuscation(bundlePath, bundleSourceMapPath) {
|
|
479
|
+
validateIfJscramblerWasApplied();
|
|
321
480
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
481
|
+
console.log(
|
|
482
|
+
instrument
|
|
483
|
+
? 'info Jscrambler Instrumenting Code'
|
|
484
|
+
: `info Jscrambler Obfuscating Code ${
|
|
485
|
+
config.enabledHermes
|
|
486
|
+
? "(Using Hermes Engine)"
|
|
487
|
+
: "(If you are using Hermes Engine set enabledHermes=true)"
|
|
488
|
+
}`,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
const isCodeHardeningThresholdSupported =
|
|
492
|
+
await jscrambler.introspectFieldOnMethod.call(
|
|
493
|
+
jscrambler,
|
|
494
|
+
config,
|
|
495
|
+
'mutation',
|
|
496
|
+
'createApplicationProtection',
|
|
497
|
+
'codeHardeningThreshold',
|
|
330
498
|
);
|
|
331
499
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
500
|
+
handleHermesIncompatibilities(config, isCodeHardeningThresholdSupported);
|
|
501
|
+
await obfuscateBundle(
|
|
502
|
+
{bundlePath, bundleSourceMapPath},
|
|
503
|
+
{fileNames: Array.from(fileNames), entryPointCode, isCodeHardeningThresholdSupported},
|
|
504
|
+
sourceMapFiles,
|
|
505
|
+
config,
|
|
506
|
+
projectRoot,
|
|
507
|
+
);
|
|
340
508
|
|
|
341
|
-
|
|
342
|
-
|
|
509
|
+
// clear obfuscation state to allow multiple builds at once
|
|
510
|
+
fileNames.clear();
|
|
511
|
+
sourceMapFiles.splice(0);
|
|
512
|
+
entryPointCode = undefined;
|
|
513
|
+
}
|
|
343
514
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
process.exit(1);
|
|
349
|
-
} finally {
|
|
350
|
-
process.exit(exitCode)
|
|
351
|
-
}
|
|
352
|
-
});
|
|
515
|
+
function applyJscramblerSerializerToModule(_module) {
|
|
516
|
+
calledByMetro = true;
|
|
517
|
+
const modulePath = _module.path;
|
|
518
|
+
const shouldSkipModule = !validateModule(modulePath, config, projectRoot);
|
|
353
519
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (shouldSkipModule) {
|
|
370
|
-
return true;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const normalizePath = buildNormalizePath(modulePath, projectRoot);
|
|
374
|
-
fileNames.add(normalizePath);
|
|
375
|
-
|
|
376
|
-
_module.output.forEach(({data}) => {
|
|
377
|
-
if (instrument && Array.isArray(data.map)) {
|
|
378
|
-
sourceMapFiles.push({
|
|
379
|
-
filename: `${normalizePath}.map`,
|
|
380
|
-
content: buildModuleSourceMap(
|
|
381
|
-
data,
|
|
382
|
-
normalizePath,
|
|
383
|
-
_module.getSource().toString()
|
|
384
|
-
)
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
if (modulePath.includes(INIT_CORE_MODULE)){
|
|
388
|
-
entryPointCode = data.code;
|
|
389
|
-
}
|
|
390
|
-
data.code = wrapCodeWithTags(data.code);
|
|
520
|
+
if (shouldSkipModule) {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
const normalizePath = buildNormalizePath(modulePath, projectRoot);
|
|
524
|
+
fileNames.add(normalizePath);
|
|
525
|
+
|
|
526
|
+
_module.output.forEach(({data}) => {
|
|
527
|
+
if (instrument && Array.isArray(data.map)) {
|
|
528
|
+
sourceMapFiles.push({
|
|
529
|
+
filename: `${normalizePath}.map`,
|
|
530
|
+
content: buildModuleSourceMap(
|
|
531
|
+
data,
|
|
532
|
+
normalizePath,
|
|
533
|
+
_module.getSource().toString()
|
|
534
|
+
)
|
|
391
535
|
});
|
|
392
|
-
return true;
|
|
393
536
|
}
|
|
394
|
-
|
|
537
|
+
if (modulePath.includes(INIT_CORE_MODULE) && entryPointCode === undefined) {
|
|
538
|
+
entryPointCode = data.code;
|
|
539
|
+
}
|
|
540
|
+
data.code = wrapCodeWithTags(data.code);
|
|
541
|
+
});
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const options = {
|
|
546
|
+
applyJscramblerSerializerToModule,
|
|
547
|
+
runObfuscation,
|
|
548
|
+
projectRoot,
|
|
395
549
|
};
|
|
550
|
+
|
|
551
|
+
const useSaveHook = isVegaBuild() || isExpoBuild();
|
|
552
|
+
|
|
553
|
+
return useSaveHook
|
|
554
|
+
? setupViaSaveHook(options)
|
|
555
|
+
: setupViaBeforeExitEvent(options);
|
|
396
556
|
};
|
package/lib/utils.js
CHANGED
|
@@ -19,12 +19,15 @@ const {
|
|
|
19
19
|
BUNDLE_OUTPUT_CLI_ARG,
|
|
20
20
|
BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG,
|
|
21
21
|
BUNDLE_DEV_CLI_ARG,
|
|
22
|
+
BUNDLE_EAGER_CLI_ARG,
|
|
22
23
|
HERMES_SHOW_SOURCE_DIRECTIVE,
|
|
23
|
-
|
|
24
|
+
VEGA_BUNDLE_CMDS,
|
|
25
|
+
EXPO_BUNDLE_CMDS,
|
|
26
|
+
BUNDLE_CMDS
|
|
24
27
|
} = require('./constants');
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
|
-
* Only
|
|
30
|
+
* Only known Metro bundle CLI subcommands trigger obfuscation (see BUNDLE_CMDS).
|
|
28
31
|
* Development bundles will be ignored (--dev true). Use JSCRAMBLER_METRO_DEV to override this behaviour.
|
|
29
32
|
* @returns {string} skip reason. If falsy value dont skip obfuscation
|
|
30
33
|
*/
|
|
@@ -39,14 +42,22 @@ function skipObfuscation(config) {
|
|
|
39
42
|
|
|
40
43
|
let isBundleCmd = false;
|
|
41
44
|
const command = new Command();
|
|
45
|
+
BUNDLE_CMDS.forEach(bundleCmd => {
|
|
46
|
+
command
|
|
47
|
+
.command(bundleCmd)
|
|
48
|
+
.allowUnknownOption()
|
|
49
|
+
.action(() => (isBundleCmd = true));
|
|
50
|
+
});
|
|
42
51
|
command
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
command.option(`${BUNDLE_DEV_CLI_ARG} <boolean>`).parse(process.argv);
|
|
52
|
+
.option(`${BUNDLE_DEV_CLI_ARG} <boolean>`)
|
|
53
|
+
.option(BUNDLE_EAGER_CLI_ARG)
|
|
54
|
+
.parse(process.argv);
|
|
47
55
|
if (!isBundleCmd) {
|
|
48
56
|
return 'Not a *bundle* command';
|
|
49
57
|
}
|
|
58
|
+
if (command.eager) {
|
|
59
|
+
return 'warning: Jscrambler Obfuscation SKIPPED [Eager export:embed]';
|
|
60
|
+
}
|
|
50
61
|
if (command.dev === 'true') {
|
|
51
62
|
return (
|
|
52
63
|
process.env.JSCRAMBLER_METRO_DEV !== 'true' &&
|
|
@@ -58,6 +69,8 @@ function skipObfuscation(config) {
|
|
|
58
69
|
|
|
59
70
|
/**
|
|
60
71
|
* Get bundle path based CLI arguments
|
|
72
|
+
* Only for some android/iOS builds, VegaOS
|
|
73
|
+
* does not reach this logic.
|
|
61
74
|
* @returns {{bundlePath: string, bundleSourceMapPath: string}}
|
|
62
75
|
* @throws {Error} when bundle output was not found
|
|
63
76
|
*/
|
|
@@ -77,6 +90,20 @@ function getBundlePath() {
|
|
|
77
90
|
return process.exit(-1);
|
|
78
91
|
}
|
|
79
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Check if contains {VEGA_BUNDLE_CMDS} commands.
|
|
95
|
+
*/
|
|
96
|
+
function isVegaBuild() {
|
|
97
|
+
return VEGA_BUNDLE_CMDS.some((cmd) => process.argv.includes(cmd));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if contains {EXPO_BUNDLE_CMDS} commands.
|
|
102
|
+
*/
|
|
103
|
+
function isExpoBuild() {
|
|
104
|
+
return EXPO_BUNDLE_CMDS.some((cmd) => process.argv.includes(cmd));
|
|
105
|
+
}
|
|
106
|
+
|
|
80
107
|
/**
|
|
81
108
|
* Extract the lines of code for a given string.
|
|
82
109
|
* @param {string} inputStr
|
|
@@ -401,6 +428,31 @@ function handleHermesIncompatibilities(
|
|
|
401
428
|
}
|
|
402
429
|
}
|
|
403
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Resolve Metro's bundle output module for expo and VegaOS.
|
|
433
|
+
* @param {string} projectRoot
|
|
434
|
+
* @returns {object}
|
|
435
|
+
*/
|
|
436
|
+
function resolveMetroOutputBundle(projectRoot) {
|
|
437
|
+
const moduleCandidates = [
|
|
438
|
+
"@expo/metro/metro/shared/output/bundle", // expo
|
|
439
|
+
"metro/src/shared/output/bundle", // pre react native 0.83
|
|
440
|
+
"metro/private/shared/output/bundle" // TODO: Check this path for react native 0.83+
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
for (const moduleId of moduleCandidates) {
|
|
444
|
+
try {
|
|
445
|
+
return require(
|
|
446
|
+
require.resolve(moduleId, {
|
|
447
|
+
paths: [projectRoot],
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
} catch (_) {
|
|
451
|
+
/* empty */
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
404
456
|
module.exports = {
|
|
405
457
|
buildModuleSourceMap,
|
|
406
458
|
buildNormalizePath,
|
|
@@ -416,5 +468,8 @@ module.exports = {
|
|
|
416
468
|
handleAntiTampering,
|
|
417
469
|
addHermesShowSourceDirective,
|
|
418
470
|
handleHermesIncompatibilities,
|
|
419
|
-
|
|
471
|
+
isVegaBuild,
|
|
472
|
+
wrapCodeWithTags,
|
|
473
|
+
isExpoBuild,
|
|
474
|
+
resolveMetroOutputBundle
|
|
420
475
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jscrambler-metro-plugin",
|
|
3
|
-
"version": "9.0
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "A plugin to use metro with Jscrambler Code Integrity",
|
|
5
5
|
"exports": "./lib/index.js",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"metro-source-map": "0.x"
|
|
7
|
+
"metro-source-map": "0.x",
|
|
8
|
+
"@react-native/metro-config": "*",
|
|
9
|
+
"expo": "*"
|
|
8
10
|
},
|
|
9
11
|
"dependencies": {
|
|
10
12
|
"commander": "^2.20.0",
|
|
@@ -15,6 +17,8 @@
|
|
|
15
17
|
"jscrambler",
|
|
16
18
|
"javascript",
|
|
17
19
|
"react-native",
|
|
20
|
+
"vega-os",
|
|
21
|
+
"expo",
|
|
18
22
|
"metro",
|
|
19
23
|
"obfuscate",
|
|
20
24
|
"protect",
|