jscrambler-metro-plugin 9.0.3 → 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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # ![Jscrambler](https://media.jscrambler.com/images/logo_500px.png)
2
- # Jscrambler Code Integrity for React-Native (Metro Bundler)
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 **React Native** bundle using Jscrambler.
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
- Include the plugin in your `metro.config.js` and add the following code:
24
+ First install the **jscrambler-metro-plugin** as a development dependency
25
25
 
26
- ```js
27
- const {resolve} = require('path');
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
- You can pass your Jscrambler configuration using the plugin parameter or using
49
- the usual `.jscramblerrc` file.
30
+ Then, set up the plugin in your `metro.config.js` by adding the following code:
50
31
 
51
- If you use a different location for the `.jscramblerignore` file, you can use the `ignoreFile` option to tell Jscrambler the path to the file.
52
- Otherwise, if a `.jscramblerignore` file is found in a project root folder, it will be considered. You can find more information and examples in Ignoring Files.
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
- "sourceMaps": true,
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
- const BUNDLE_CMD = 'bundle';
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
- BUNDLE_CMD,
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
- process.on('beforeExit', async function (exitCode) {
317
- try{
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
- console.log(
323
- instrument
324
- ? 'info Jscrambler Instrumenting Code'
325
- : `info Jscrambler Obfuscating Code ${
326
- config.enabledHermes
327
- ? "(Using Hermes Engine)"
328
- : "(If you are using Hermes Engine set enabledHermes=true)"
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
- const isCodeHardeningThresholdSupported =
333
- await jscrambler.introspectFieldOnMethod.call(
334
- jscrambler,
335
- config,
336
- 'mutation',
337
- 'createApplicationProtection',
338
- 'codeHardeningThreshold',
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
- // check for incompatible transformations and turn off code hardening
342
- handleHermesIncompatibilities(config, isCodeHardeningThresholdSupported);
509
+ // clear obfuscation state to allow multiple builds at once
510
+ fileNames.clear();
511
+ sourceMapFiles.splice(0);
512
+ entryPointCode = undefined;
513
+ }
343
514
 
344
- // start obfuscation
345
- await obfuscateBundle(bundlePath, {fileNames: Array.from(fileNames), entryPointCode, isCodeHardeningThresholdSupported}, sourceMapFiles, config, projectRoot);
346
- } catch(err) {
347
- console.error(err);
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
- return {
355
- serializer: {
356
- /**
357
- * Select user files ONLY (no vendor) to be obfuscated. That code should be tagged with
358
- * {@JSCRAMBLER_BEG_ANNOTATION} and {@JSCRAMBLER_END_ANNOTATION}.
359
- * Also gather metro source-maps in case of instrumentation process.
360
- * @param {{output: Array<*>, path: string, getSource: function():Buffer}} _module
361
- * @returns {boolean}
362
- */
363
- processModuleFilter(_module) {
364
- calledByMetro = true;
365
-
366
- const modulePath = _module.path;
367
- const shouldSkipModule = !validateModule(modulePath, config, projectRoot);
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
- BUNDLE_CMD
24
+ VEGA_BUNDLE_CMDS,
25
+ EXPO_BUNDLE_CMDS,
26
+ BUNDLE_CMDS
24
27
  } = require('./constants');
25
28
 
26
29
  /**
27
- * Only 'bundle' command triggers obfuscation.
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
- .command(BUNDLE_CMD)
44
- .allowUnknownOption()
45
- .action(() => (isBundleCmd = true));
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
- wrapCodeWithTags
471
+ isVegaBuild,
472
+ wrapCodeWithTags,
473
+ isExpoBuild,
474
+ resolveMetroOutputBundle
420
475
  };
package/package.json CHANGED
@@ -1,20 +1,24 @@
1
1
  {
2
2
  "name": "jscrambler-metro-plugin",
3
- "version": "9.0.3",
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",
11
13
  "fs-extra": "^8.0.1",
12
- "jscrambler": "8.22.0"
14
+ "jscrambler": "8.22.1"
13
15
  },
14
16
  "keywords": [
15
17
  "jscrambler",
16
18
  "javascript",
17
19
  "react-native",
20
+ "vega-os",
21
+ "expo",
18
22
  "metro",
19
23
  "obfuscate",
20
24
  "protect",