diff --git a/.circleci/config.yml b/.circleci/config.yml index 3293c65d1..29c44c948 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -411,12 +411,18 @@ jobs: - run: name: Build the SDK command: yarn build + - run: + name: Get snapshot version + command: | + source scripts/snapshot-version.sh + echo "export SNAPSHOT_VERSION=$SNAPSHOT_VERSION" >> "$BASH_ENV" - run: name: Authorize with NPM command: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + - run: npm version $SNAPSHOT_VERSION --no-git-tag-version - run: name: Publish new enterprise version - command: npm publish + command: npm publish --tag snapshot publish: macos: @@ -547,16 +553,10 @@ workflows: - hold_release_nn: requires: *release_dependencies type: approval - filters: - branches: - only: master - release_custom_package: name: release_nn requires: - hold_release_nn - filters: - branches: - only: master prepare_steps: - prepare_custom_package: npm_package: '@instabug/react-native-nn' diff --git a/android/sourcemaps.gradle b/android/sourcemaps.gradle index f544974d2..4bc9e9e66 100644 --- a/android/sourcemaps.gradle +++ b/android/sourcemaps.gradle @@ -1,6 +1,6 @@ import org.apache.tools.ant.taskdefs.condition.Os -gradle.projectsEvaluated { +project.afterEvaluate { // Works for both `bundleReleaseJsAndAssets` and `createBundleReleaseJsAndAssets` and product flavors def suffix = 'ReleaseJsAndAssets' def bundleTasks = project(':app').tasks.findAll { @@ -15,11 +15,13 @@ gradle.projectsEvaluated { def flavor = name.substring(start, end).uncapitalize() def defaultVersion = getDefaultVersion(flavor) - task.finalizedBy createUploadSourcemapsTask(flavor, defaultVersion.name, defaultVersion.code) + + + task.finalizedBy createUploadSourcemapsTask(flavor, defaultVersion.name, defaultVersion.code,task) } } -Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String defaultVersionCode) { +Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String defaultVersionCode, Task task) { def name = 'uploadSourcemaps' + flavor.capitalize() // Don't recreate the task if it already exists. @@ -39,18 +41,26 @@ Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String try { def appProject = project(':app') def appDir = appProject.projectDir - def sourceMapFile = getSourceMapFile(appDir, flavor) + def sourceMapFile = getSourceMapFile(appDir, flavor,task) + println "✅ Resolved sourcemap file path: ${sourceMapFile.absolutePath}" def jsProjectDir = rootDir.parentFile def instabugDir = new File(['node', '-p', 'require.resolve("instabug-reactnative/package.json")'].execute(null, rootDir).text.trim()).getParentFile() - def tokenShellFile = new File(instabugDir, 'scripts/find-token.sh') - def inferredToken = executeShellScript(tokenShellFile, jsProjectDir) + def tokenJsFile = new File(instabugDir, 'scripts/find-token.js') + def inferredToken = executeNodeScript(tokenJsFile, jsProjectDir) + + if (!inferredToken) { + println "❌ Unable to infer Instabug token from script: ${tokenShellFile.absolutePath}" + } + def appToken = resolveVar('App Token', 'INSTABUG_APP_TOKEN', inferredToken) def versionName = resolveVar('Version Name', 'INSTABUG_VERSION_NAME', defaultVersionName) def versionCode = resolveVar('Version Code', 'INSTABUG_VERSION_CODE', defaultVersionCode) + println "📦 Uploading with versionName=${versionName}, versionCode=${versionCode}, appToken=${appToken.take(5)}..." + exec { def osCompatibility = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c'] : [] def args = [ @@ -67,6 +77,7 @@ Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String } catch (exception) { project.logger.error "Failed to upload source map file.\n" + "Reason: ${exception.message}" + } } } @@ -74,27 +85,48 @@ Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String return provider.get() } -File getSourceMapFile(File appDir, String flavor) { +File getSourceMapFile(File appDir, String flavor, Task task) { def defaultFlavorPath = flavor.empty ? 'release' : "${flavor}Release" def defaultSourceMapDest = "build/generated/sourcemaps/react/${defaultFlavorPath}/index.android.bundle.map" def defaultSourceMapFile = new File(appDir, defaultSourceMapDest) + def props = task.getProperties() + + def bundleAssetName = props.containsKey("bundleAssetName") ? props.bundleAssetName?.getOrNull() : null + def jsSourceMapsDir = props.containsKey("jsSourceMapsDir") ? props.jsSourceMapsDir?.getOrNull() : null + def jsIntermediateSourceMapsDir = props.containsKey("jsIntermediateSourceMapsDir") ? props.jsIntermediateSourceMapsDir?.getOrNull() : null + + if (bundleAssetName && jsSourceMapsDir) { + def outputSourceMap = new File(jsSourceMapsDir.asFile.absolutePath, "${bundleAssetName}.map") + if (outputSourceMap.exists()) { + return outputSourceMap + } + } + + if (bundleAssetName && jsIntermediateSourceMapsDir) { + def packagerOutputSourceMap = new File(jsIntermediateSourceMapsDir.asFile.absolutePath, "${bundleAssetName}.packager.map") + if (packagerOutputSourceMap.exists()) { + return packagerOutputSourceMap + } + } if (defaultSourceMapFile.exists()) { return defaultSourceMapFile } if (flavor.empty) { - throw new InvalidUserDataException("Unable to find source map file at: ${defaultSourceMapFile.absolutePath}.") + println"Source map file not found at: ${defaultSourceMapFile.absolutePath}. Skipping." + return null } def fallbackSourceMapDest = "build/generated/sourcemaps/react/${flavor}/release/index.android.bundle.map" def fallbackSourceMapFile = new File(appDir, fallbackSourceMapDest) - project.logger.info "Unable to find source map file at: ${defaultSourceMapFile.absolutePath}.\n" + + println "Unable to find source map file at: ${defaultSourceMapFile.absolutePath}.\n" + "Falling back to ${fallbackSourceMapFile.absolutePath}." if (!fallbackSourceMapFile.exists()) { - throw new InvalidUserDataException("Unable to find source map file at: ${fallbackSourceMapFile.absolutePath} either.") + println "Fallback source map file not found at: ${fallbackSourceMapFile.absolutePath}. Skipping." + return null } return fallbackSourceMapFile @@ -148,21 +180,63 @@ String resolveVar(String name, String envKey, String defaultValue) { def value = envValue ?: defaultValue if (value == null) { - throw new InvalidUserDataException("Unable to find ${name}! " + - "Set the environment variable `${envKey}` and try again.") + println "Unable to find ${name}! " + + "Set the environment variable `${envKey}` and try again." } return value } +static String executeNodeScript(File script, File workingDir) { + if (!script.exists()) { + println "Script not found: ${script.absolutePath}" + return null + } + + def output = new StringBuffer() + def error = new StringBuffer() + + try { + def process = ['node', script.getAbsolutePath()].execute(null, workingDir) + process.waitForProcessOutput(output, error) + + if (process.exitValue() != 0) { + println "Script failed with exit code ${process.exitValue()}" + println "Standard Error:\n${error.toString().trim()}" + println "Standard Output:\n${output.toString().trim()}" + return null + } + + return output.toString().trim() + + } catch (Exception e) { + println "Exception while executing Node.js script: ${e.message}" + e.printStackTrace() + return null + } +} + static String executeShellScript(File script, File workingDir) { if (Os.isFamily(Os.FAMILY_WINDOWS)) { return null } + if (!script.canExecute()) { + // Try to set executable permission + script.setExecutable(true) + } + def output = new StringBuffer() + def error = new StringBuffer() + + // Using 'sh' instead of './' to avoid needing exec permission, but keeping chmod above just in case def process = ['sh', script.getAbsolutePath()].execute(null, workingDir) - process?.waitForProcessOutput(output, new StringBuffer()) + process?.waitForProcessOutput(output, error) + + if (process.exitValue() != 0) { + println "Error running script: ${error.toString().trim()}" + return null + } - return process?.exitValue() == 0 ? output.toString().trim() : null + return output.toString().trim() } diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java index 72872fac8..04ee09b80 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java @@ -18,13 +18,16 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UIManager; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.UIManagerModule; import com.instabug.apm.InternalAPM; import com.instabug.apm.configuration.cp.APMFeature; +import com.instabug.bug.BugReporting; import com.instabug.library.Feature; import com.instabug.library.Instabug; import com.instabug.library.InstabugColorTheme; @@ -112,6 +115,7 @@ public void removeListeners(Integer count) { /** * Enables or disables Instabug functionality. + * * @param isEnabled A boolean to enable/disable Instabug. */ @ReactMethod @@ -120,7 +124,7 @@ public void setEnabled(final boolean isEnabled) { @Override public void run() { try { - if(isEnabled) + if (isEnabled) Instabug.enable(); else Instabug.disable(); @@ -133,10 +137,11 @@ public void run() { /** * Initializes the SDK. - * @param token The token that identifies the app. You can find it on your dashboard. + * + * @param token The token that identifies the app. You can find it on your dashboard. * @param invocationEventValues The events that invoke the SDK's UI. - * @param logLevel The level of detail in logs that you want to print. - * @param codePushVersion The Code Push version to be used for all reports. + * @param logLevel The level of detail in logs that you want to print. + * @param codePushVersion The Code Push version to be used for all reports. */ @ReactMethod public void init( @@ -162,8 +167,8 @@ public void run() { .setInvocationEvents(invocationEvents) .setLogLevel(parsedLogLevel); - if(codePushVersion != null) { - if(Instabug.isBuilt()) { + if (codePushVersion != null) { + if (Instabug.isBuilt()) { Instabug.setCodePushVersion(codePushVersion); } else { builder.setCodePushVersion(codePushVersion); @@ -329,7 +334,7 @@ public void run() { * * @param userEmail User's default email * @param userName Username. - * @param userId User's ID + * @param userId User's ID */ @ReactMethod public void identifyUser( @@ -749,15 +754,15 @@ public void addFileAttachmentWithDataToReport(String data, String fileName) { private WritableMap convertFromHashMapToWriteableMap(HashMap hashMap) { WritableMap writableMap = new WritableNativeMap(); - for(int i = 0; i < hashMap.size(); i++) { + for (int i = 0; i < hashMap.size(); i++) { Object key = hashMap.keySet().toArray()[i]; Object value = hashMap.get(key); - writableMap.putString((String) key,(String) value); + writableMap.putString((String) key, (String) value); } return writableMap; } - private static JSONObject objectToJSONObject(Object object){ + private static JSONObject objectToJSONObject(Object object) { Object json = null; JSONObject jsonObject = null; try { @@ -774,13 +779,12 @@ private static JSONObject objectToJSONObject(Object object){ private WritableArray convertArrayListToWritableArray(List arrayList) { WritableArray writableArray = new WritableNativeArray(); - for(int i = 0; i < arrayList.size(); i++) { + for (int i = 0; i < arrayList.size(); i++) { Object object = arrayList.get(i); - if(object instanceof String) { + if (object instanceof String) { writableArray.pushString((String) object); - } - else { + } else { JSONObject jsonObject = objectToJSONObject(object); writableArray.pushMap((WritableMap) jsonObject); } @@ -836,7 +840,7 @@ public void run() { * Shows the welcome message in a specific mode. * * @param welcomeMessageMode An enum to set the welcome message mode to - * live, or beta. + * live, or beta. */ @ReactMethod public void showWelcomeMessageWithMode(final String welcomeMessageMode) { @@ -858,7 +862,7 @@ public void run() { * Sets the welcome message mode to live, beta or disabled. * * @param welcomeMessageMode An enum to set the welcome message mode to - * live, beta or disabled. + * live, beta or disabled. */ @ReactMethod public void setWelcomeMessageMode(final String welcomeMessageMode) { @@ -946,14 +950,22 @@ public void networkLogAndroid(final String url, @UiThread @Nullable private View resolveReactView(final int reactTag) { - final ReactApplicationContext reactContext = getReactApplicationContext(); - final UIManagerModule uiManagerModule = reactContext.getNativeModule(UIManagerModule.class); + try { + final ReactApplicationContext reactContext = getReactApplicationContext(); + final UIManagerModule uiManagerModule = reactContext.getNativeModule(UIManagerModule.class); - if (uiManagerModule == null) { + if (uiManagerModule == null) { + UIManager uiNewManagerModule = UIManagerHelper.getUIManagerForReactTag(reactContext, reactTag); + if (uiNewManagerModule != null) { + return uiNewManagerModule.resolveView(reactTag); + } + return null; + } + + return uiManagerModule.resolveView(reactTag); + } catch (Exception e) { return null; } - - return uiManagerModule.resolveView(reactTag); } @@ -993,7 +1005,6 @@ public void run() { * Reports that the screen name been changed (Current View). * * @param screenName string containing the screen name - * */ @ReactMethod public void reportCurrentViewChange(final String screenName) { @@ -1016,7 +1027,6 @@ public void run() { * Reports that the screen has been changed (Repro Steps) the screen sent to this method will be the 'current view' on the dashboard * * @param screenName string containing the screen name - * */ @ReactMethod public void reportScreenChange(final String screenName) { @@ -1026,7 +1036,7 @@ public void run() { try { Method method = getMethod(Class.forName("com.instabug.library.Instabug"), "reportScreenChange", Bitmap.class, String.class); if (method != null) { - method.invoke(null , null, screenName); + method.invoke(null, null, screenName); } } catch (Exception e) { e.printStackTrace(); @@ -1120,7 +1130,7 @@ public void removeFeatureFlags(final ReadableArray featureFlags) { @Override public void run() { try { - ArrayList stringArray = ArrayUtil.parseReadableArrayOfStrings(featureFlags); + ArrayList stringArray = ArrayUtil.parseReadableArrayOfStrings(featureFlags); Instabug.removeFeatureFlag(stringArray); } catch (Exception e) { e.printStackTrace(); @@ -1156,11 +1166,12 @@ public void run() { } }); } + /** * Register a listener for W3C flags value change */ @ReactMethod - public void registerW3CFlagsChangeListener(){ + public void registerW3CFlagsChangeListener() { MainThreadHandler.runOnMainThread(new Runnable() { @Override @@ -1177,8 +1188,7 @@ public void invoke(@NonNull CoreFeaturesState featuresState) { sendEvent(Constants.IBG_ON_NEW_W3C_FLAGS_UPDATE_RECEIVED_CALLBACK, params); } }); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } @@ -1189,18 +1199,17 @@ public void invoke(@NonNull CoreFeaturesState featuresState) { /** - * Get first time Value of W3ExternalTraceID flag + * Get first time Value of W3ExternalTraceID flag */ @ReactMethod - public void isW3ExternalTraceIDEnabled(Promise promise){ + public void isW3ExternalTraceIDEnabled(Promise promise) { MainThreadHandler.runOnMainThread(new Runnable() { @Override public void run() { try { promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_EXTERNAL_TRACE_ID)); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); promise.resolve(false); } @@ -1212,18 +1221,17 @@ public void run() { /** - * Get first time Value of W3ExternalGeneratedHeader flag + * Get first time Value of W3ExternalGeneratedHeader flag */ @ReactMethod - public void isW3ExternalGeneratedHeaderEnabled(Promise promise){ + public void isW3ExternalGeneratedHeaderEnabled(Promise promise) { MainThreadHandler.runOnMainThread(new Runnable() { @Override public void run() { try { promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_GENERATED_HEADER)); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); promise.resolve(false); } @@ -1234,18 +1242,17 @@ public void run() { } /** - * Get first time Value of W3CaughtHeader flag + * Get first time Value of W3CaughtHeader flag */ @ReactMethod - public void isW3CaughtHeaderEnabled(Promise promise){ + public void isW3CaughtHeaderEnabled(Promise promise) { MainThreadHandler.runOnMainThread(new Runnable() { @Override public void run() { try { promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_CAPTURED_HEADER)); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); promise.resolve(false); } @@ -1260,7 +1267,7 @@ public void run() { * Map between the exported JS constant and the arg key in {@link ArgsRegistry}. * The constant name and the arg key should match to be able to resolve the * constant with its actual value from the {@link ArgsRegistry} maps. - * + *

* This is a workaround, because RN cannot resolve enums in the constants map. */ @Override @@ -1275,21 +1282,22 @@ public Map getConstants() { return constants; } - /** - * Enables or disables capturing network body. - * @param isEnabled A boolean to enable/disable capturing network body. - */ - @ReactMethod - public void setNetworkLogBodyEnabled(final boolean isEnabled) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - Instabug.setNetworkLogBodyEnabled(isEnabled); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } + /** + * Enables or disables capturing network body. + * + * @param isEnabled A boolean to enable/disable capturing network body. + */ + @ReactMethod + public void setNetworkLogBodyEnabled(final boolean isEnabled) { + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + Instabug.setNetworkLogBodyEnabled(isEnabled); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } }