jscrambler-metro-plugin 0.0.0-bulbasaur-20250620144942

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jscrambler
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # ![Jscrambler](https://media.jscrambler.com/images/logo_500px.png)
2
+ # Jscrambler Code Integrity for React-Native (Metro Bundler)
3
+
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
+
6
+ If you're looking to gain control over third-party tags and achieve PCI DSS compliance please refer to Jscrambler [Webpage Integrity](https://jscrambler.com/webpage-integrity).
7
+
8
+ Version Compatibility
9
+ ------------------------------------------------------------------------------
10
+
11
+ The version's compatibility table match your [Jscrambler Version](https://app.jscrambler.com/settings) with the Jscrambler Metro Plugin.
12
+ Please make sure you install the right version, otherwise some functionalities might not work properly.
13
+
14
+ | _Jscrambler Version_ | _Client and Integrations_ |
15
+ |:----------:|:-------------:|
16
+ | _<= 7.1_ | _<= 5.x.x_ |
17
+ | _\>= 7.2_ | _\>= 6.0.0_ |
18
+
19
+ # Usage
20
+
21
+ This metro plugin protects your **React Native** bundle using Jscrambler.
22
+
23
+ Include the plugin in your `metro.config.js` and add the following code:
24
+
25
+ ```js
26
+ const {resolve} = require('path');
27
+ const jscramblerMetroPlugin = require('jscrambler-metro-plugin')(
28
+ /* optional */
29
+ {
30
+ enable: true,
31
+ enabledHermes: false, // set if you are using hermes engine
32
+ ignoreFile: resolve(__dirname, '.jscramblerignore'),
33
+ params: [
34
+ {
35
+ name: 'selfDefending',
36
+ options: {
37
+ threshold: 1
38
+ }
39
+ }
40
+ ]
41
+ }
42
+ );
43
+
44
+ module.exports = jscramblerMetroPlugin;
45
+ ```
46
+
47
+ You can pass your Jscrambler configuration using the plugin parameter or using
48
+ the usual `.jscramblerrc` file.
49
+
50
+ If you use a different location for the `.jscramblerignore` file, you can use the `ignoreFile` option to tell Jscrambler the path to the file.
51
+ 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.
52
+
53
+ 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`
54
+
55
+ 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:
56
+
57
+ ...
58
+ "sourceMaps": true,
59
+ ...
60
+
61
+ and in the [React Native app](https://reactnative.dev/docs/debugging-release-builds?platform=android#enabling-source-maps).
@@ -0,0 +1,62 @@
1
+ const path = require('path');
2
+
3
+ const BUNDLE_CMD = 'bundle';
4
+ const BUNDLE_OUTPUT_CLI_ARG = '--bundle-output';
5
+ const BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG = '--sourcemap-output';
6
+ const BUNDLE_DEV_CLI_ARG = '--dev';
7
+ // path.join so it supports both linux and windows fs
8
+ const INIT_CORE_MODULE = path.join(process.platform === 'win32' ? '/' : '', 'node_modules', 'react-native', 'Libraries', 'Core', 'InitializeCore.js');
9
+ const JSCRAMBLER_CLIENT_ID = 6;
10
+ const JSCRAMBLER_IGNORE = '.jscramblerignore';
11
+ const JSCRAMBLER_TEMP_FOLDER = '.jscrambler';
12
+ const JSCRAMBLER_DIST_TEMP_FOLDER = `${JSCRAMBLER_TEMP_FOLDER}/dist`;
13
+ const JSCRAMBLER_SOURCE_MAPS_TEMP_FOLDER = `${JSCRAMBLER_DIST_TEMP_FOLDER}/jscramblerSourceMaps`;
14
+ const JSCRAMBLER_PROTECTION_ID_FILE = `${JSCRAMBLER_TEMP_FOLDER}/protectionId`;
15
+ const JSCRAMBLER_BEG_ANNOTATION = '"JSCRAMBLER-BEG";';
16
+ const JSCRAMBLER_END_ANNOTATION = '"JSCRAMBLER-END";';
17
+ const JSCRAMBLER_EXTS = /.(j|t)s(x)?$/i;
18
+ const JSCRAMBLER_SELF_DEFENDING = 'selfDefending';
19
+ const JSCRAMBLER_ANTI_TAMPERING = 'antiTampering';
20
+ const JSCRAMBLER_SELF_HEALING = "selfHealing";
21
+ const JSCRAMBLER_ANTI_TAMPERING_MODE_RCK = 'RCK';
22
+ const JSCRAMBLER_ANTI_TAMPERING_MODE_SKL = 'SKL';
23
+ const JSCRAMBLER_GLOBAL_VARIABLE_INDIRECTION = 'globalVariableIndirection';
24
+ const JSCRAMBLER_TOLERATE_BENIGN_POISONING = 'tolerateBenignPoisoning';
25
+ const HERMES_SHOW_SOURCE_DIRECTIVE = '"show source";';
26
+ const JSCRAMBLER_HERMES_INCOMPATIBILITIES = [
27
+ {
28
+ slugName: JSCRAMBLER_SELF_DEFENDING,
29
+ errorMessage: `Jscrambler ${JSCRAMBLER_SELF_DEFENDING} transformation is not compatible with Hermes engine. Consider using ${JSCRAMBLER_ANTI_TAMPERING} transformation instead`,
30
+ },
31
+ ];
32
+ const JSCRAMBLER_HERMES_ADD_SHOW_SOURCE_DIRECTIVE = [
33
+ JSCRAMBLER_ANTI_TAMPERING,
34
+ JSCRAMBLER_SELF_HEALING
35
+ ];
36
+
37
+ module.exports = {
38
+ BUNDLE_CMD,
39
+ BUNDLE_OUTPUT_CLI_ARG,
40
+ BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG,
41
+ BUNDLE_DEV_CLI_ARG,
42
+ INIT_CORE_MODULE,
43
+ JSCRAMBLER_CLIENT_ID,
44
+ JSCRAMBLER_IGNORE,
45
+ JSCRAMBLER_TEMP_FOLDER,
46
+ JSCRAMBLER_DIST_TEMP_FOLDER,
47
+ JSCRAMBLER_SOURCE_MAPS_TEMP_FOLDER,
48
+ JSCRAMBLER_PROTECTION_ID_FILE,
49
+ JSCRAMBLER_BEG_ANNOTATION,
50
+ JSCRAMBLER_END_ANNOTATION,
51
+ JSCRAMBLER_SELF_DEFENDING,
52
+ JSCRAMBLER_GLOBAL_VARIABLE_INDIRECTION,
53
+ JSCRAMBLER_TOLERATE_BENIGN_POISONING,
54
+ JSCRAMBLER_ANTI_TAMPERING,
55
+ JSCRAMBLER_ANTI_TAMPERING_MODE_RCK,
56
+ JSCRAMBLER_SELF_HEALING,
57
+ JSCRAMBLER_HERMES_INCOMPATIBILITIES,
58
+ JSCRAMBLER_HERMES_ADD_SHOW_SOURCE_DIRECTIVE,
59
+ JSCRAMBLER_ANTI_TAMPERING_MODE_SKL,
60
+ HERMES_SHOW_SOURCE_DIRECTIVE,
61
+ JSCRAMBLER_EXTS
62
+ }
package/lib/index.js ADDED
@@ -0,0 +1,377 @@
1
+ const {copy, emptyDir, mkdirp, readFile, writeFile} = require('fs-extra');
2
+ const jscrambler = require('jscrambler').default;
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const generateSourceMaps = require('./sourceMaps');
6
+ const globalThisPolyfill = require('./polyfills/globalThis');
7
+
8
+ const {
9
+ INIT_CORE_MODULE,
10
+ JSCRAMBLER_CLIENT_ID,
11
+ JSCRAMBLER_TEMP_FOLDER,
12
+ JSCRAMBLER_IGNORE,
13
+ JSCRAMBLER_DIST_TEMP_FOLDER,
14
+ JSCRAMBLER_PROTECTION_ID_FILE,
15
+ JSCRAMBLER_BEG_ANNOTATION,
16
+ JSCRAMBLER_END_ANNOTATION,
17
+ BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG,
18
+ HERMES_SHOW_SOURCE_DIRECTIVE,
19
+ JSCRAMBLER_EXTS
20
+ } = require('./constants');
21
+ const {
22
+ buildModuleSourceMap,
23
+ buildNormalizePath,
24
+ extractLocs,
25
+ getBundlePath,
26
+ isFileReadable,
27
+ skipObfuscation,
28
+ stripEntryPointTags,
29
+ stripJscramblerTags,
30
+ addBundleArgsToExcludeList,
31
+ handleExcludeList,
32
+ injectTolerateBegninPoisoning,
33
+ handleAntiTampering,
34
+ addHermesShowSourceDirective,
35
+ handleHermesIncompatibilities,
36
+ wrapCodeWithTags
37
+ } = require('./utils');
38
+
39
+ const debug = !!process.env.DEBUG;
40
+
41
+ function logSourceMapsWarning(hasMetroSourceMaps, hasJscramblerSourceMaps) {
42
+ if (hasMetroSourceMaps) {
43
+ console.log(`warning: Jscrambler source-maps are DISABLED. Check how to activate them in https://docs.jscrambler.com/code-integrity/documentation/source-maps/api`);
44
+ } else if (hasJscramblerSourceMaps) {
45
+ console.log(`warning: Jscrambler source-maps were not generated. Missing metro source-maps (${BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG} is required)`);
46
+ }
47
+ }
48
+
49
+ async function obfuscateBundle(
50
+ {bundlePath, bundleSourceMapPath},
51
+ {fileNames, entryPointCode},
52
+ sourceMapFiles,
53
+ config,
54
+ projectRoot
55
+ ) {
56
+ await emptyDir(JSCRAMBLER_TEMP_FOLDER);
57
+
58
+ const metroBundle = await readFile(bundlePath, 'utf8');
59
+ const metroBundleLocs = await extractLocs(metroBundle);
60
+ let processedMetroBundle = metroBundle;
61
+ let filteredFileNames = fileNames;
62
+ const excludeList = [];
63
+
64
+ const supportsEntryPoint = await jscrambler.introspectFieldOnMethod.call(
65
+ jscrambler,
66
+ config,
67
+ "mutation",
68
+ "createApplicationProtection",
69
+ "entryPoint"
70
+ );
71
+
72
+ // ignore entrypoint obfuscation if its not supported
73
+ if (!supportsEntryPoint) {
74
+ delete config.entryPoint;
75
+ if (typeof entryPointCode === 'string' && entryPointCode.length > 0) {
76
+ debug && console.log('debug Jscrambler entrypoint option not supported');
77
+ try {
78
+ filteredFileNames = fileNames.filter(
79
+ name => !name.includes(INIT_CORE_MODULE)
80
+ );
81
+ processedMetroBundle = stripEntryPointTags(
82
+ metroBundle,
83
+ entryPointCode
84
+ );
85
+ } catch (err) {
86
+ console.log("Error processing entry point.");
87
+ process.exit(-1);
88
+ }
89
+ }
90
+ }
91
+
92
+ const metroBundleChunks = processedMetroBundle.split(
93
+ JSCRAMBLER_BEG_ANNOTATION
94
+ );
95
+ addBundleArgsToExcludeList(metroBundleChunks[0], excludeList);
96
+ const metroUserFilesOnly = metroBundleChunks.slice(1).map((c, i) => {
97
+ const s = c.split(JSCRAMBLER_END_ANNOTATION);
98
+ // We don't want to extract args from last chunk
99
+ if (i < metroBundleChunks.length - 2) {
100
+ addBundleArgsToExcludeList(s[1], excludeList);
101
+ }
102
+ return s[0];
103
+ });
104
+
105
+ const sources = [];
106
+ // .jscramblerignore
107
+ const defaultJscramblerIgnorePath = path.join(projectRoot, JSCRAMBLER_IGNORE);
108
+
109
+ if (typeof config.ignoreFile === 'string') {
110
+ if (!await isFileReadable(config.ignoreFile)) {
111
+ console.error(`The *ignoreFile* "${config.ignoreFile}" was not found or is not readable!`);
112
+ process.exit(-1);
113
+ }
114
+ sources.push({ filename: JSCRAMBLER_IGNORE, content: await readFile(config.ignoreFile) })
115
+ } else if (await isFileReadable(defaultJscramblerIgnorePath)) {
116
+ sources.push({ filename: JSCRAMBLER_IGNORE, content: await readFile(defaultJscramblerIgnorePath) })
117
+ }
118
+
119
+ // push user files to sources array
120
+ for (let i = 0; i < metroUserFilesOnly.length; i += 1) {
121
+ sources.push({
122
+ filename: filteredFileNames[i], content: metroUserFilesOnly[i]
123
+ })
124
+ }
125
+
126
+ // Source map files (only for Instrumentation process)
127
+ for (const { filename, content } of sourceMapFiles) {
128
+ sources.push({
129
+ filename, content
130
+ })
131
+ }
132
+
133
+ // adapt configs for react-native
134
+ config.sources = sources;
135
+ config.filesDest = JSCRAMBLER_DIST_TEMP_FOLDER;
136
+ config.clientId = JSCRAMBLER_CLIENT_ID;
137
+
138
+ const supportsExcludeList = await jscrambler.introspectFieldOnMethod.call(
139
+ jscrambler,
140
+ config,
141
+ "mutation",
142
+ "createApplicationProtection",
143
+ "excludeList"
144
+ );
145
+
146
+ handleExcludeList(config, {supportsExcludeList, excludeList});
147
+
148
+ injectTolerateBegninPoisoning(config);
149
+
150
+ if (bundleSourceMapPath && typeof config.sourceMaps === 'undefined') {
151
+ console.error(`error Metro is generating source maps that won't be useful after Jscrambler protection.
152
+ If this is not a problem, you can either:
153
+ 1) Disable source maps in metro bundler
154
+ 2) Explicitly disable Jscrambler source maps by adding 'sourceMaps: false' in the Jscrambler config file
155
+
156
+ If you want valid source maps, make sure you have access to the feature and enable it in Jscrambler config file by adding 'sourceMaps: true'`
157
+ );
158
+ process.exit(-1);
159
+ }
160
+
161
+ const requireStartAtFirstColumn = handleAntiTampering(
162
+ config,
163
+ processedMetroBundle,
164
+ );
165
+
166
+ const addShowSource = addHermesShowSourceDirective(config);
167
+
168
+ if (addShowSource) {
169
+ console.log(
170
+ `info Jscrambler ${HERMES_SHOW_SOURCE_DIRECTIVE} directive added`,
171
+ );
172
+ }
173
+
174
+ const shouldGenerateSourceMaps = config.sourceMaps && bundleSourceMapPath;
175
+
176
+ const jscramblerOp = !!config.instrument
177
+ ? jscrambler.instrumentAndDownload
178
+ : jscrambler.protectAndDownload;
179
+
180
+ // obfuscate or instrument
181
+ const protectionId = await jscramblerOp.call(jscrambler, config);
182
+
183
+ // store protection id
184
+ await writeFile(JSCRAMBLER_PROTECTION_ID_FILE, protectionId);
185
+
186
+ // read obfuscated user files
187
+ const obfusctedUserFiles = await Promise.all(metroUserFilesOnly.map((c, i) =>
188
+ readFile(`${JSCRAMBLER_DIST_TEMP_FOLDER}/${filteredFileNames[i]}`, 'utf8')
189
+ ));
190
+
191
+ // build final bundle (with JSCRAMBLER TAGS still)
192
+ const finalBundle = metroBundleChunks.reduce((acc, c, i) => {
193
+ if (i === 0) {
194
+ const chunks = c.split('\n');
195
+ return [`${chunks[0]}${globalThisPolyfill}`, ...chunks.slice(1)].join('\n');
196
+ }
197
+
198
+ let showSource = addShowSource;
199
+ let startAtFirstColumn = requireStartAtFirstColumn;
200
+
201
+ const obfuscatedCode = obfusctedUserFiles[i - 1];
202
+ const sourceFileIgnored = metroUserFilesOnly[i - 1] === obfuscatedCode;
203
+
204
+ if (sourceFileIgnored) {
205
+ // restore excluded files
206
+ showSource = false;
207
+ startAtFirstColumn = false;
208
+ debug && console.log(`debug Jscrambler File ${fileNames[i - 1]} was excluded`);
209
+ }
210
+
211
+ const tillCodeEnd = c.substr(
212
+ c.indexOf(JSCRAMBLER_END_ANNOTATION),
213
+ c.length
214
+ );
215
+ return `${acc}${JSCRAMBLER_BEG_ANNOTATION}${
216
+ showSource ? HERMES_SHOW_SOURCE_DIRECTIVE : ''
217
+ }${startAtFirstColumn ? '\n' : ''}${obfuscatedCode}${tillCodeEnd}`;
218
+ }, '');
219
+
220
+ await writeFile(bundlePath, stripJscramblerTags(finalBundle));
221
+ if(!shouldGenerateSourceMaps) {
222
+ logSourceMapsWarning(bundleSourceMapPath, config.sourceMaps);
223
+ // nothing more to do
224
+ return;
225
+ }
226
+
227
+ // process Jscrambler SourceMaps
228
+ const shouldAddSourceContent = typeof config.sourceMaps === 'object' ? config.sourceMaps.sourceContent : false;
229
+ console.log(`info Jscrambler Source Maps (${shouldAddSourceContent ? "with" : "no"} source content)`);
230
+ const finalSourceMap = await generateSourceMaps({
231
+ jscrambler,
232
+ config,
233
+ shouldAddSourceContent,
234
+ protectionId,
235
+ metroUserFilesOnly,
236
+ fileNames: filteredFileNames,
237
+ bundlePath,
238
+ bundleSourceMapPath,
239
+ finalBundle,
240
+ projectRoot,
241
+ debug,
242
+ metroBundleLocs
243
+ });
244
+ await writeFile(bundleSourceMapPath, finalSourceMap);
245
+ }
246
+
247
+ function fileExists(modulePath) {
248
+ return fs.existsSync(modulePath);
249
+ }
250
+
251
+ function isValidExtension(modulePath) {
252
+ return path.extname(modulePath).match(JSCRAMBLER_EXTS);
253
+ }
254
+
255
+ function validateModule(modulePath, config, projectRoot) {
256
+ const instrument = !!config.instrument;
257
+
258
+ if (
259
+ !fileExists(modulePath) ||
260
+ !isValidExtension(modulePath) ||
261
+ typeof modulePath !== "string"
262
+ ) {
263
+ return false;
264
+ } else if (modulePath.includes(INIT_CORE_MODULE) && !instrument) {
265
+ // This is the entrypoint file
266
+ config.entryPoint = buildNormalizePath(modulePath, projectRoot);
267
+ return true;
268
+ } else if (modulePath.includes("node_modules")) {
269
+ return false;
270
+ } else {
271
+ return true;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Add serialize.processModuleFilter option to metro and attach listener to beforeExit event.
277
+ * *config.fileSrc* and *config.filesDest* will be ignored.
278
+ * @param {{enable: boolean, enabledHermes: boolean }} _config
279
+ * @param {string} [projectRoot=process.cwd()]
280
+ * @returns {{serializer: {processModuleFilter(*): boolean}}}
281
+ */
282
+ module.exports = function (_config = {}, projectRoot = process.cwd()) {
283
+ const skipReason = skipObfuscation(_config);
284
+ if (skipReason) {
285
+ console.log(`warning: Jscrambler Obfuscation SKIPPED [${skipReason}]`);
286
+ return {};
287
+ }
288
+
289
+ const bundlePath = getBundlePath();
290
+ // make sure jscrambler-metro-plugin is properly configure on metro bundler
291
+ let calledByMetro = false;
292
+ const fileNames = new Set();
293
+ const sourceMapFiles = [];
294
+ const config = Object.assign({}, jscrambler.config, _config);
295
+ const instrument = !!config.instrument;
296
+ let entryPointCode;
297
+
298
+ if (config.filesDest || config.filesSrc) {
299
+ console.warn('warning: Jscrambler fields filesDest and fileSrc were ignored. Using input/output values of the metro bundler.')
300
+ }
301
+
302
+ if (!Array.isArray(config.params) || config.params.length === 0) {
303
+ console.warn('warning: Jscrambler recommends you to declare your transformations list on the configuration file.')
304
+ }
305
+
306
+ process.on('beforeExit', async function (exitCode) {
307
+ try{
308
+ if (!calledByMetro) {
309
+ 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.');
310
+ }
311
+
312
+ console.log(
313
+ instrument
314
+ ? 'info Jscrambler Instrumenting Code'
315
+ : `info Jscrambler Obfuscating Code ${
316
+ config.enabledHermes
317
+ ? "(Using Hermes Engine)"
318
+ : "(If you are using Hermes Engine set enabledHermes=true)"
319
+ }`,
320
+ );
321
+
322
+ // check for incompatible transformations and turn off code hardening
323
+ handleHermesIncompatibilities(config);
324
+
325
+ // start obfuscation
326
+ await obfuscateBundle(bundlePath, {fileNames: Array.from(fileNames), entryPointCode}, sourceMapFiles, config, projectRoot);
327
+ } catch(err) {
328
+ console.error(err);
329
+ process.exit(1);
330
+ } finally {
331
+ process.exit(exitCode)
332
+ }
333
+ });
334
+
335
+ return {
336
+ serializer: {
337
+ /**
338
+ * Select user files ONLY (no vendor) to be obfuscated. That code should be tagged with
339
+ * {@JSCRAMBLER_BEG_ANNOTATION} and {@JSCRAMBLER_END_ANNOTATION}.
340
+ * Also gather metro source-maps in case of instrumentation process.
341
+ * @param {{output: Array<*>, path: string, getSource: function():Buffer}} _module
342
+ * @returns {boolean}
343
+ */
344
+ processModuleFilter(_module) {
345
+ calledByMetro = true;
346
+
347
+ const modulePath = _module.path;
348
+ const shouldSkipModule = !validateModule(modulePath, config, projectRoot);
349
+
350
+ if (shouldSkipModule) {
351
+ return true;
352
+ }
353
+
354
+ const normalizePath = buildNormalizePath(modulePath, projectRoot);
355
+ fileNames.add(normalizePath);
356
+
357
+ _module.output.forEach(({data}) => {
358
+ if (instrument && Array.isArray(data.map)) {
359
+ sourceMapFiles.push({
360
+ filename: `${normalizePath}.map`,
361
+ content: buildModuleSourceMap(
362
+ data,
363
+ normalizePath,
364
+ _module.getSource().toString()
365
+ )
366
+ });
367
+ }
368
+ if (modulePath.includes(INIT_CORE_MODULE)){
369
+ entryPointCode = data.code;
370
+ }
371
+ data.code = wrapCodeWithTags(data.code);
372
+ });
373
+ return true;
374
+ }
375
+ }
376
+ };
377
+ };
@@ -0,0 +1 @@
1
+ module.exports = "!(function(o){o.globalThis=o})('undefined'!=typeof globalThis?globalThis:'undefined'!=typeof global?global:'undefined'!=typeof window?window:this);";
@@ -0,0 +1,179 @@
1
+ const {readFile} = require('fs-extra');
2
+ const sourceMap = require('source-map');
3
+ const {
4
+ buildNormalizePath,
5
+ extractLocs
6
+ } = require('./utils');
7
+ const {
8
+ JSCRAMBLER_SOURCE_MAPS_TEMP_FOLDER
9
+ } = require('./constants');
10
+
11
+ /**
12
+ * Merge jscrambler source-maps with metro source-map.
13
+ * Control start/end line of each obfuscated source and calculate amount of lines that needs to
14
+ * be shifted for none-obfuscated code.
15
+ * @param {object} payload
16
+ * @returns {Promise<string>}
17
+ */
18
+ module.exports = async function generateSourceMaps(payload) {
19
+ const {
20
+ jscrambler,
21
+ config,
22
+ shouldAddSourceContent,
23
+ protectionId,
24
+ metroUserFilesOnly,
25
+ fileNames,
26
+ debug,
27
+ bundlePath,
28
+ bundleSourceMapPath,
29
+ finalBundle,
30
+ projectRoot,
31
+ metroBundleLocs
32
+ } = payload;
33
+
34
+ // download sourcemaps
35
+ delete config.filesSrc;
36
+ await jscrambler.downloadSourceMaps(Object.assign({protectionId}, config));
37
+
38
+ // read obfuscated source-map from filesystem
39
+ const obfuscatedSourceMaps = await Promise.all(
40
+ metroUserFilesOnly.map((c, i) =>
41
+ readFile(
42
+ `${JSCRAMBLER_SOURCE_MAPS_TEMP_FOLDER}/${fileNames[i]}.map`,
43
+ 'utf8',
44
+ ).catch((e) => {
45
+ if (e.code === 'ENOENT') {
46
+ // when a source file is excluded, the sourcemap is not produced
47
+ return null;
48
+ }
49
+ throw e;
50
+ }),
51
+ ),
52
+ );
53
+
54
+ // read metro source-map
55
+ const metroSourceMap = await readFile(bundleSourceMapPath, 'utf8');
56
+ const finalBundleLocs = await extractLocs(finalBundle);
57
+
58
+ const metroSourceMapConsumer = new sourceMap.SourceMapConsumer(
59
+ metroSourceMap,
60
+ );
61
+ // extra source map params that are not processed by the source-map package
62
+ const metroSourceMapExtraParams = [
63
+ 'x_facebook_sources', // added x_facebook_sources to prevent the third party sources to come back with null values
64
+ // for when react native debugIds are necessary in the sourcemaps (upload to sentry for example)
65
+ // needs to be added at the end of this file since the debugIds are not in SourceMapGenerator type
66
+ 'debugId',
67
+ 'debug_id',
68
+ ];
69
+ // retrieve debug ids and facebook sources after the validation by the sourcemap package
70
+ const JSONMetroSourceMap = JSON.parse(metroSourceMap);
71
+ const finalSourceMapGenerator = new sourceMap.SourceMapGenerator({
72
+ file: bundlePath,
73
+ });
74
+ const ofuscatedSourceMapConsumers = obfuscatedSourceMaps.map((map) =>
75
+ // when a source file is excluded, the sourcemap is not produced
76
+ map ? new sourceMap.SourceMapConsumer(map) : null,
77
+ );
78
+
79
+ // add all original sources and sourceContents
80
+ metroSourceMapConsumer.sources.forEach(function (sourceFile) {
81
+ finalSourceMapGenerator._sources.add(sourceFile)
82
+ var sourceContent = metroSourceMapConsumer.sourceContentFor(sourceFile);
83
+ if (shouldAddSourceContent && sourceContent != null) {
84
+ finalSourceMapGenerator.setSourceContent(sourceFile, sourceContent)
85
+ }
86
+ });
87
+
88
+ let shiftLines = 0;
89
+ let tmpShiftLine = 0;
90
+ let currSource;
91
+ metroSourceMapConsumer.eachMapping(mapping => {
92
+ const original = mapping.originalLine ? {line: mapping.originalLine, column: mapping.originalColumn} : null;
93
+ let newMappings = [{
94
+ original,
95
+ source: mapping.source,
96
+ name: mapping.name,
97
+ generated: {
98
+ line: mapping.generatedLine,
99
+ column: mapping.generatedColumn
100
+ }
101
+ }];
102
+ const normalizePath = buildNormalizePath(mapping.source, projectRoot);
103
+ const fileNamesIndex = fileNames.indexOf(normalizePath);
104
+
105
+ if (currSource !== normalizePath) {
106
+ // next source
107
+ currSource = normalizePath;
108
+ shiftLines = tmpShiftLine;
109
+ }
110
+
111
+ if (
112
+ fileNamesIndex !== -1 &&
113
+ /* check if sourceMap was loaded */
114
+ ofuscatedSourceMapConsumers[fileNamesIndex]
115
+ ) {
116
+ /* jscrambler obfuscated files */
117
+ const {lineStart, lineEnd, columnStart} = metroBundleLocs[fileNamesIndex];
118
+ const {
119
+ lineStart: finalLineStart,
120
+ lineEnd: finalLineEnd,
121
+ columnStart: finalColumnStart,
122
+ } = finalBundleLocs[fileNamesIndex];
123
+ const allGeneratedPositionsFor = ofuscatedSourceMapConsumers[fileNamesIndex].allGeneratedPositionsFor({
124
+ source: normalizePath,
125
+ line: mapping.generatedLine - lineStart + 1 /* avoid line=0 */,
126
+ column:
127
+ mapping.generatedColumn -
128
+ (mapping.generatedLine === lineStart
129
+ ? columnStart /* column start should be applied only to the first line */
130
+ : 0),
131
+ });
132
+
133
+ if (allGeneratedPositionsFor.length === 0) {
134
+ // no match
135
+ return;
136
+ }
137
+
138
+ newMappings = allGeneratedPositionsFor.map(({line: obfLine, column: obfColumn}) => {
139
+ const calcFinalLine = finalLineStart + obfLine - 1;
140
+ // add columnStart only on the first line
141
+ const calcFinalColumn = obfLine === 1 ? finalColumnStart + obfColumn : obfColumn;
142
+
143
+ debug && console.log('original', original, '->', 'final', {line: calcFinalLine, column: calcFinalColumn});
144
+
145
+ return Object.assign({}, newMappings[0], {
146
+ generated: {
147
+ line: calcFinalLine,
148
+ column: calcFinalColumn
149
+ }
150
+ });
151
+ });
152
+
153
+ // shift lines on next files
154
+ tmpShiftLine = finalLineEnd - lineEnd;
155
+ } else {
156
+ /* vendor code */
157
+ newMappings[0].generated.line += shiftLines;
158
+
159
+ // when the original line/column can't be found, it means that there isn't a real source file associated.
160
+ // Thus, if source file name exists it must be cleaned (f.e ".") to avoid an invalid mapping error
161
+ if (newMappings[0].original === null && newMappings[0].source) {
162
+ newMappings[0].source = null;
163
+ }
164
+ }
165
+
166
+ newMappings.forEach((newMapping) =>
167
+ finalSourceMapGenerator.addMapping(newMapping),
168
+ );
169
+ })
170
+
171
+ const finalSourceMaps = finalSourceMapGenerator.toString();
172
+ const finalSourceMapsJson = JSON.parse(finalSourceMaps);
173
+
174
+ for (const param of metroSourceMapExtraParams) {
175
+ finalSourceMapsJson[param] = JSONMetroSourceMap[param];
176
+ }
177
+
178
+ return JSON.stringify(finalSourceMapsJson);
179
+ };
package/lib/utils.js ADDED
@@ -0,0 +1,401 @@
1
+ const fs = require('fs');
2
+ const readline = require('readline');
3
+ const {Command} = require('commander');
4
+ const {Readable} = require('stream');
5
+ const { sep } = require('path');
6
+ const metroSourceMap = require('metro-source-map');
7
+ const {
8
+ JSCRAMBLER_EXTS,
9
+ JSCRAMBLER_END_ANNOTATION,
10
+ JSCRAMBLER_BEG_ANNOTATION,
11
+ JSCRAMBLER_SELF_DEFENDING,
12
+ JSCRAMBLER_ANTI_TAMPERING,
13
+ JSCRAMBLER_HERMES_INCOMPATIBILITIES,
14
+ JSCRAMBLER_HERMES_ADD_SHOW_SOURCE_DIRECTIVE,
15
+ JSCRAMBLER_ANTI_TAMPERING_MODE_SKL,
16
+ JSCRAMBLER_ANTI_TAMPERING_MODE_RCK,
17
+ JSCRAMBLER_TOLERATE_BENIGN_POISONING,
18
+ JSCRAMBLER_GLOBAL_VARIABLE_INDIRECTION,
19
+ BUNDLE_OUTPUT_CLI_ARG,
20
+ BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG,
21
+ BUNDLE_DEV_CLI_ARG,
22
+ HERMES_SHOW_SOURCE_DIRECTIVE,
23
+ BUNDLE_CMD
24
+ } = require('./constants');
25
+
26
+ /**
27
+ * Only 'bundle' command triggers obfuscation.
28
+ * Development bundles will be ignored (--dev true). Use JSCRAMBLER_METRO_DEV to override this behaviour.
29
+ * @returns {string} skip reason. If falsy value dont skip obfuscation
30
+ */
31
+ function skipObfuscation(config) {
32
+ if (
33
+ typeof config === 'object' &&
34
+ config !== null &&
35
+ config.enable === false
36
+ ) {
37
+ return 'Explicitly Disabled';
38
+ }
39
+
40
+ let isBundleCmd = false;
41
+ const command = new Command();
42
+ command
43
+ .command(BUNDLE_CMD)
44
+ .allowUnknownOption()
45
+ .action(() => (isBundleCmd = true));
46
+ command.option(`${BUNDLE_DEV_CLI_ARG} <boolean>`).parse(process.argv);
47
+ if (!isBundleCmd) {
48
+ return 'Not a *bundle* command';
49
+ }
50
+ if (command.dev === 'true') {
51
+ return (
52
+ process.env.JSCRAMBLER_METRO_DEV !== 'true' &&
53
+ 'Development mode. Override with JSCRAMBLER_METRO_DEV=true environment variable'
54
+ );
55
+ }
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Get bundle path based CLI arguments
61
+ * @returns {{bundlePath: string, bundleSourceMapPath: string}}
62
+ * @throws {Error} when bundle output was not found
63
+ */
64
+ function getBundlePath() {
65
+ const command = new Command();
66
+ command
67
+ .option(`${BUNDLE_OUTPUT_CLI_ARG} <string>`)
68
+ .option(`${BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG} <string>`)
69
+ .parse(process.argv);
70
+ if (command.bundleOutput) {
71
+ return {
72
+ bundlePath: command.bundleOutput,
73
+ bundleSourceMapPath: command.sourcemapOutput
74
+ };
75
+ }
76
+ console.error('Bundle output path not found.');
77
+ return process.exit(-1);
78
+ }
79
+
80
+ /**
81
+ * Extract the lines of code for a given string.
82
+ * @param {string} inputStr
83
+ * @returns {Promise<[{lineStart: number, columnStart: number, lineEnd: number}]>}
84
+ */
85
+ function extractLocs(inputStr) {
86
+ let locs = [];
87
+ let lines = 0;
88
+ return new Promise((res, rej) =>
89
+ readline.createInterface({
90
+ input: new Readable({
91
+ read() {
92
+ this.push(this.sent ? null : inputStr);
93
+ this.sent = true;
94
+ }
95
+ }),
96
+ crlfDelay: Infinity,
97
+ terminal: false,
98
+ historySize: 0
99
+ })
100
+ .on('line', line => {
101
+ lines++;
102
+ const startTagIndex = line.indexOf(JSCRAMBLER_BEG_ANNOTATION);
103
+ if (startTagIndex !== -1) {
104
+ const columnStart = line.includes(
105
+ `${JSCRAMBLER_BEG_ANNOTATION}${HERMES_SHOW_SOURCE_DIRECTIVE}`,
106
+ )
107
+ ? HERMES_SHOW_SOURCE_DIRECTIVE.length + startTagIndex
108
+ : startTagIndex;
109
+ // occurs with Anti-tampering SKL mode
110
+ const startAtFirstColumn = line.includes(
111
+ `${JSCRAMBLER_BEG_ANNOTATION}\n`,
112
+ );
113
+
114
+ locs.push({
115
+ lineStart: lines,
116
+ columnStart,
117
+ startAtFirstColumn,
118
+ });
119
+ }
120
+
121
+ if (line.indexOf(JSCRAMBLER_END_ANNOTATION) !== -1) {
122
+ locs[locs.length - 1].lineEnd = lines;
123
+ }
124
+ })
125
+ .on('close', () => res(locs))
126
+ .on('error', rej)
127
+ )
128
+ }
129
+
130
+ /**
131
+ * Strip all Jscrambler tags from code
132
+ * @param {string} code
133
+ * @returns {string}
134
+ */
135
+ function stripJscramblerTags(code) {
136
+ return code.replace(new RegExp(JSCRAMBLER_BEG_ANNOTATION, 'g'), '')
137
+ .replace(new RegExp(JSCRAMBLER_END_ANNOTATION, 'g'), '')
138
+ }
139
+
140
+ /**
141
+ * When next character is a new line (\n or \r\n),
142
+ * we should increment startIndex to avoid user code starting with a new line.
143
+ * @param {string} startIndex
144
+ * @param {string} code
145
+ * @returns {number}
146
+ * @example
147
+ * __d(function(g,r,i,a,m,e,d){(detect new line here and start below)
148
+ * // user code
149
+ * ...
150
+ * }
151
+ */
152
+ function shiftStartIndexOnNewLine(startIndex, code) {
153
+ switch (code[startIndex + 1]) {
154
+ case '\r':
155
+ startIndex++;
156
+ return shiftStartIndexOnNewLine(startIndex, code);
157
+ case '\n':
158
+ startIndex++;
159
+ break;
160
+ }
161
+ return startIndex;
162
+ }
163
+
164
+ /**
165
+ * Wrap code with Jscrambler TAGS {JSCRAMBLER_BEG_ANNOTATION and JSCRAMBLER_END_ANNOTATION}
166
+ * @param {string} code
167
+ */
168
+ function wrapCodeWithTags(code) {
169
+ let startIndex = code.indexOf('{');
170
+ const endIndex = code.lastIndexOf('}');
171
+ startIndex = shiftStartIndexOnNewLine(startIndex, code);
172
+ const init = code.substring(0, startIndex + 1);
173
+ const clientCode = code.substring(startIndex + 1, endIndex);
174
+ const end = code.substr(endIndex, code.length);
175
+ const codeWithTags = init + JSCRAMBLER_BEG_ANNOTATION + clientCode + JSCRAMBLER_END_ANNOTATION + end;
176
+
177
+ return codeWithTags;
178
+ }
179
+
180
+ /**
181
+ * Use 'metro-source-map' to build a standard source-map from raw mappings
182
+ * @param {{code: string, map: Array.<Array<number>>}} output
183
+ * @param {string} modulePath
184
+ * @param {string} source
185
+ * @returns {string}
186
+ */
187
+ function buildModuleSourceMap(output, modulePath, source) {
188
+ return metroSourceMap
189
+ .fromRawMappings([
190
+ {
191
+ ...output,
192
+ source,
193
+ path: modulePath
194
+ }
195
+ ])
196
+ .toString(modulePath);
197
+ }
198
+
199
+ /**
200
+ * @param {string} path
201
+ * @param {string} projectRoot
202
+ * @returns {string} undefined if path is empty or invalid
203
+ *
204
+ * @example
205
+ * <project_root>/react-native0.59-grocery-list/App/index.js -> App/index.js
206
+ * <project_root>/react-native0.59-grocery-list/App/index.ts -> App/index.js
207
+ */
208
+ function buildNormalizePath(path, projectRoot) {
209
+ if (typeof path !== 'string' || path.trim().length === 0) {
210
+ return;
211
+ }
212
+ const relativePath = path.replace(projectRoot, '');
213
+ let relativePathWithLeadingSlash = relativePath.replace(JSCRAMBLER_EXTS, '.js');
214
+ if (relativePathWithLeadingSlash.startsWith(sep)) {
215
+ relativePathWithLeadingSlash = relativePathWithLeadingSlash.substring(1 /* remove leading separator */);
216
+ }
217
+ return relativePathWithLeadingSlash.replace(/\\/g, '/'); // replace win32 separator by linux one
218
+ }
219
+
220
+ function getCodeBody(code) {
221
+ const bodyBegIndex = code.indexOf("{");
222
+ const bodyEndIndex = code.lastIndexOf("}");
223
+ // +1 to include last '}'
224
+ return code.substring(bodyBegIndex, bodyEndIndex + 1);
225
+ }
226
+
227
+ function stripEntryPointTags(metroBundle, entryPointMinified) {
228
+ const entryPointBody = getCodeBody(entryPointMinified);
229
+ const entryPointBodyWithTags = wrapCodeWithTags(entryPointBody);
230
+ const metroChunksByEntrypoint = metroBundle.split(entryPointBodyWithTags);
231
+ // restore entrypoint original code
232
+ metroChunksByEntrypoint.splice(1, 0, entryPointBody);
233
+ return metroChunksByEntrypoint.join('');
234
+ }
235
+
236
+ /**
237
+ * Check if some file is readable
238
+ * @param {string} path filename path to be tested
239
+ * @returns {Promise<boolean>} true if readable, otherwise false
240
+ */
241
+ const isFileReadable = (path) => new Promise((resolve) => {
242
+ fs.access(path, fs.constants.F_OK | fs.constants.R_OK, error => resolve(!error))
243
+ })
244
+
245
+ const addBundleArgsToExcludeList = (chunk, excludeList) => {
246
+ const regex = /\(([0-9a-zA-Z_,$ ]+)\)[ ]?{$/gm;
247
+ const m = regex.exec(chunk);
248
+ if (Array.isArray(m) && m.length > 1) {
249
+ for (const arg of m[m.length - 1].split(",")) {
250
+ if (!excludeList.includes(arg.trim())) {
251
+ excludeList.push(arg.trim());
252
+ }
253
+ }
254
+ return;
255
+ }
256
+
257
+ console.error(`Unable to add global variables to the exclude list.`);
258
+ process.exit(1);
259
+ };
260
+
261
+ function handleExcludeList(config, {supportsExcludeList, excludeList}) {
262
+ if (supportsExcludeList) {
263
+ config.excludeList = excludeList;
264
+ } else {
265
+ // add excludeList to gvi in case the api does not support global excludeList
266
+ if (Array.isArray(config.params)) {
267
+ const gvi = config.params.find(
268
+ (param) => param.name === JSCRAMBLER_GLOBAL_VARIABLE_INDIRECTION
269
+ );
270
+ if (gvi) {
271
+ gvi.options = gvi.options || {};
272
+ const mixedList = [
273
+ ...new Set(excludeList.concat(gvi.options.excludeList || [])),
274
+ ];
275
+ gvi.options.excludeList = mixedList;
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ function injectTolerateBegninPoisoning(config) {
282
+ if (Array.isArray(config.params)) {
283
+ const sd = config.params.find(
284
+ (param) => param.name === JSCRAMBLER_SELF_DEFENDING
285
+ );
286
+ if (sd) {
287
+ sd.options = sd.options || {};
288
+ sd.options.options = sd.options.options || [];
289
+ if (
290
+ Array.isArray(sd.options.options) &&
291
+ !sd.options.options.includes(JSCRAMBLER_TOLERATE_BENIGN_POISONING)
292
+ ) {
293
+ console.log(`info Jscrambler Tolerate benign poisoning option was automatically added to Self-Defending.`);
294
+ sd.options.options.push(JSCRAMBLER_TOLERATE_BENIGN_POISONING)
295
+ }
296
+ }
297
+ }
298
+ }
299
+
300
+ /**
301
+ * @param {object} config
302
+ * @param {string} processedMetroBundle
303
+ * @returns {boolean} if true the code must start in the first column
304
+ */
305
+ function handleAntiTampering(config, processedMetroBundle) {
306
+ let requireStartAtFirstColumn = false
307
+ if (Array.isArray(config.params)) {
308
+ const antiTampering = config.params.find(
309
+ (param) => param.name === JSCRAMBLER_ANTI_TAMPERING
310
+ );
311
+ if (antiTampering) {
312
+ antiTampering.options = antiTampering.options || {};
313
+ antiTampering.options.mode = antiTampering.options.mode || [JSCRAMBLER_ANTI_TAMPERING_MODE_RCK, JSCRAMBLER_ANTI_TAMPERING_MODE_SKL];
314
+ if (config.enabledHermes) {
315
+ if (
316
+ Array.isArray(antiTampering.options.mode) &&
317
+ antiTampering.options.mode.includes(JSCRAMBLER_ANTI_TAMPERING_MODE_SKL)
318
+ ) {
319
+ console.log(`info Jscrambler Anti-Tampering Mode SKL can not be used in hermes engine. RCK mode was SET.`);
320
+ antiTampering.options.mode = [JSCRAMBLER_ANTI_TAMPERING_MODE_RCK];
321
+ }
322
+ }
323
+
324
+ if (antiTampering.options.mode.includes(JSCRAMBLER_ANTI_TAMPERING_MODE_SKL)) {
325
+ const singleLineModule = processedMetroBundle.match(RegExp(`\n\\S+${JSCRAMBLER_BEG_ANNOTATION}`, 'm'));
326
+ if (singleLineModule !== null) {
327
+ requireStartAtFirstColumn = true;
328
+ }
329
+ }
330
+ }
331
+ }
332
+ return requireStartAtFirstColumn;
333
+ }
334
+
335
+ /**
336
+ * @param {object} config
337
+ * @returns {boolean} if true 'show source' directive is added
338
+ */
339
+ function addHermesShowSourceDirective(config) {
340
+ if (!config.enabledHermes) {
341
+ return false;
342
+ }
343
+
344
+ for (const slugName of JSCRAMBLER_HERMES_ADD_SHOW_SOURCE_DIRECTIVE) {
345
+ if (Array.isArray(config.params)) {
346
+ const showSource = config.params.find((param) => param.name === slugName);
347
+ if (showSource) {
348
+ return true;
349
+ }
350
+ }
351
+ }
352
+
353
+ return false;
354
+ }
355
+
356
+ /**
357
+ * @param config
358
+ * @exception {Error} If an incompatible transformation was selected
359
+ */
360
+ function handleHermesIncompatibilities(config) {
361
+ if (!config.enabledHermes) {
362
+ return;
363
+ }
364
+
365
+ if (config.codeHardeningThreshold === undefined) {
366
+ console.log(`info Jscrambler Code Hardening ignored, as it is incompatible with hermes engine.`);
367
+ }
368
+ config.codeHardeningThreshold = 999999999;
369
+
370
+ for (const {
371
+ slugName,
372
+ errorMessage,
373
+ } of JSCRAMBLER_HERMES_INCOMPATIBILITIES) {
374
+ if (Array.isArray(config.params)) {
375
+ const usingIncompatible = config.params.find(
376
+ (param) => param.name === slugName,
377
+ );
378
+ if (usingIncompatible) {
379
+ throw new Error(errorMessage);
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ module.exports = {
386
+ buildModuleSourceMap,
387
+ buildNormalizePath,
388
+ extractLocs,
389
+ getBundlePath,
390
+ isFileReadable,
391
+ skipObfuscation,
392
+ stripEntryPointTags,
393
+ stripJscramblerTags,
394
+ addBundleArgsToExcludeList,
395
+ handleExcludeList,
396
+ injectTolerateBegninPoisoning,
397
+ handleAntiTampering,
398
+ addHermesShowSourceDirective,
399
+ handleHermesIncompatibilities,
400
+ wrapCodeWithTags
401
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "jscrambler-metro-plugin",
3
+ "version": "0.0.0-bulbasaur-20250620144942",
4
+ "description": "A plugin to use metro with Jscrambler Code Integrity",
5
+ "exports": "./lib/index.js",
6
+ "peerDependencies": {
7
+ "metro-source-map": "0.x"
8
+ },
9
+ "dependencies": {
10
+ "commander": "^2.20.0",
11
+ "fs-extra": "^8.0.1",
12
+ "jscrambler": "0.0.0-bulbasaur-20250620144942"
13
+ },
14
+ "keywords": [
15
+ "jscrambler",
16
+ "javascript",
17
+ "react-native",
18
+ "metro",
19
+ "obfuscate",
20
+ "protect",
21
+ "js"
22
+ ],
23
+ "files": [
24
+ "lib"
25
+ ],
26
+ "author": "Jscrambler <support@jscrambler.com>",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/jscrambler/jscrambler.git",
31
+ "directory": "packages/jscrambler-metro-plugin"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "registry": "https://registry.npmjs.org/"
36
+ },
37
+ "scripts": {
38
+ "eslint": "eslint lib/",
39
+ "eslint:fix": "pnpm run eslint --fix"
40
+ }
41
+ }