diff --git a/.gitignore b/.gitignore index a705ae779..3e159e30e 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ build/ .gradle local.properties *.iml +.project +.settings/ # VS Code *.save diff --git a/package.json b/package.json index 947622859..85b04a50c 100644 --- a/package.json +++ b/package.json @@ -1,84 +1,69 @@ { - "name": "dd-sdk-reactnative", - "description": "A client-side React Native module to interact with Datadog", - "keywords": [ - "datadog", - "react-native", - "ios", - "android" - ], - "author": "Datadog (https://github.com/DataDog)", - "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", - "repository": "https://github.com/DataDog/dd-sdk-reactnative", - "bugs": { - "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" - }, - "license": "Apache-2.0", - "private": true, - "workspaces": { - "packages": [ - "packages/*", - "example", - "example-new-architecture" - ] - }, - "scripts": { - "prepare": "genversion --es6 --semi packages/core/src/version.ts && lerna run prepare", - "test": "genversion --es6 --semi packages/core/src/version.ts && jest", - "lint": "genversion --es6 --semi packages/core/src/version.ts && eslint .", - "example": "yarn --cwd example", - "postinstall": "./packages/react-navigation/fix-react-navigation-import-in-dependencies.sh" - }, - "devDependencies": { - "@babel/plugin-transform-runtime": "7.12.15", - "@testing-library/react-native": "7.0.2", - "@types/jest": "^29.2.1", - "@types/react": "^18.0.24", - "@types/react-native": "0.71.0", - "@types/react-test-renderer": "18.0.0", - "@typescript-eslint/eslint-plugin": "5.20.0", - "@typescript-eslint/parser": "5.20.0", - "dd-trace": "^3.3.1", - "eslint": "8.10.0", - "eslint-config-prettier": "6.0.0", - "eslint-plugin-arca": "0.15.0", - "eslint-plugin-import": "2.25.4", - "eslint-plugin-prettier": "4.0.0", - "eslint-plugin-react": "7.22.0", - "eslint-plugin-react-hooks": "4.3.0", - "eslint-plugin-react-native": "3.10.0", - "genversion": "3.0.2", - "jest": "29.2.1", - "lerna": "7.1.0", - "metro-react-native-babel-preset": "0.73.9", - "pod-install": "0.1.14", - "prettier": "2.2.0", - "react": "18.2.0", - "react-native": "0.71.10", - "react-native-builder-bob": "0.17.1", - "react-native-webview": "11.26.1", - "react-test-renderer": "18.1.0", - "typescript": "4.8.4" - }, - "jest": { - "preset": "react-native", - "moduleNameMapper": { - "@datadog/mobile-react-native": "/packages/core/src" - }, - "modulePathIgnorePatterns": [ - "/example/node_modules", - "/packages/.*/lib/" - ], - "setupFiles": [ - "./node_modules/react-native-gesture-handler/jestSetup.js", - "./jest.setup.js" + "name": "dd-sdk-reactnative", + "description": "A client-side React Native module to interact with Datadog", + "keywords": [ + "datadog", + "react-native", + "ios", + "android" ], - "testPathIgnorePatterns": [ - "/__utils__/" - ], - "transformIgnorePatterns": [ - "jest-runner" - ] - }, - "packageManager": "yarn@3.4.1" + "author": "Datadog (https://github.com/DataDog)", + "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", + "repository": "https://github.com/DataDog/dd-sdk-reactnative", + "bugs": { + "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" + }, + "license": "Apache-2.0", + "private": true, + "workspaces": { + "packages": [ + "packages/*", + "example", + "example-new-architecture" + ] + }, + "scripts": { + "prepare": "genversion --es6 --semi packages/core/src/version.ts && lerna run prepare", + "test": "genversion --es6 --semi packages/core/src/version.ts && jest", + "lint": "genversion --es6 --semi packages/core/src/version.ts && eslint .", + "example": "yarn --cwd example", + "postinstall": "./packages/react-navigation/fix-react-navigation-import-in-dependencies.sh" + }, + "devDependencies": { + "@babel/plugin-transform-runtime": "7.12.15", + "@testing-library/react-native": "7.0.2", + "@types/jest": "^29.2.1", + "@types/react": "^18.0.24", + "@types/react-native": "0.71.0", + "@types/react-test-renderer": "18.0.0", + "@typescript-eslint/eslint-plugin": "5.20.0", + "@typescript-eslint/parser": "5.20.0", + "dd-trace": "^3.3.1", + "eslint": "8.10.0", + "eslint-config-prettier": "6.0.0", + "eslint-plugin-arca": "0.15.0", + "eslint-plugin-import": "2.25.4", + "eslint-plugin-prettier": "4.0.0", + "eslint-plugin-react": "7.22.0", + "eslint-plugin-react-hooks": "4.3.0", + "eslint-plugin-react-native": "3.10.0", + "genversion": "3.0.2", + "jest": "29.2.1", + "lerna": "7.1.0", + "metro-react-native-babel-preset": "0.73.9", + "pod-install": "0.1.14", + "prettier": "2.2.0", + "react": "18.2.0", + "react-native": "0.71.10", + "react-native-builder-bob": "0.17.1", + "react-native-webview": "11.26.1", + "react-test-renderer": "18.1.0", + "typescript": "4.8.4" + }, + "jest": { + "projects": [ + "/packages/*" + ] + }, + "packageManager": "yarn@3.4.1" } diff --git a/packages/codepush/package.json b/packages/codepush/package.json index 8cdd13d5c..a36f7c165 100644 --- a/packages/codepush/package.json +++ b/packages/codepush/package.json @@ -1,76 +1,81 @@ { - "name": "@datadog/mobile-react-native-code-push", - "version": "1.8.5", - "description": "A client-side React Native module to interact with Appcenter Codepush and Datadog", - "keywords": [ - "datadog", - "react-native", - "ios", - "android", - "codepush", - "appcenter" - ], - "author": "Datadog (https://github.com/DataDog)", - "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", - "repository": { - "url": "https://github.com/DataDog/dd-sdk-reactnative", - "directory": "packages/codepush" - }, - "bugs": { - "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" - }, - "license": "Apache-2.0", - "main": "lib/commonjs/index", - "files": [ - "src/**", - "lib/**" - ], - "types": "lib/typescript/codepush/src/index.d.ts", - "react-native": "src/index", - "source": "src/index", - "module": "lib/module/index", - "publishConfig": { - "access": "public" - }, - "scripts": { - "test": "jest", - "lint": "eslint .", - "prepare": "rm -rf lib && yarn bob build" - }, - "devDependencies": { - "@datadog/mobile-react-native": "^1.8.5", - "@testing-library/react-native": "7.0.2", - "react-native-builder-bob": "0.17.1", - "react-native-code-push": "7.0.5" - }, - "peerDependencies": { - "@datadog/mobile-react-native": "^1.0.0-beta4", - "react": ">=16.13.1", - "react-native": ">=0.63.4 <1.0", - "react-native-code-push": ">=2.0.0" - }, - "jest": { - "preset": "react-native", - "modulePathIgnorePatterns": [ - "/lib/" + "name": "@datadog/mobile-react-native-code-push", + "version": "1.8.5", + "description": "A client-side React Native module to interact with Appcenter Codepush and Datadog", + "keywords": [ + "datadog", + "react-native", + "ios", + "android", + "codepush", + "appcenter" ], - "testPathIgnorePatterns": [ - "/__utils__/" + "author": "Datadog (https://github.com/DataDog)", + "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", + "repository": { + "url": "https://github.com/DataDog/dd-sdk-reactnative", + "directory": "packages/codepush" + }, + "bugs": { + "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" + }, + "license": "Apache-2.0", + "main": "lib/commonjs/index", + "files": [ + "src/**", + "lib/**" ], - "transformIgnorePatterns": [] - }, - "react-native-builder-bob": { - "source": "src", - "output": "lib", - "targets": [ - "commonjs", - "module", - [ - "typescript", - { - "tsc": "./../../node_modules/.bin/tsc" - } - ] - ] - } + "types": "lib/typescript/codepush/src/index.d.ts", + "react-native": "src/index", + "source": "src/index", + "module": "lib/module/index", + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "lint": "eslint .", + "prepare": "rm -rf lib && yarn bob build" + }, + "devDependencies": { + "@datadog/mobile-react-native": "^1.8.5", + "@testing-library/react-native": "7.0.2", + "react-native-builder-bob": "0.17.1", + "react-native-code-push": "7.0.5" + }, + "peerDependencies": { + "@datadog/mobile-react-native": "^1.0.0-beta4", + "react": ">=16.13.1", + "react-native": ">=0.63.4 <1.0", + "react-native-code-push": ">=2.0.0" + }, + "jest": { + "preset": "react-native", + "moduleNameMapper": { + "@datadog/mobile-react-native": "../core/src" + }, + "modulePathIgnorePatterns": [ + "/lib/" + ], + "testPathIgnorePatterns": [ + "/__utils__/" + ], + "transformIgnorePatterns": [ + "jest-runner" + ] + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "tsc": "./../../node_modules/.bin/tsc" + } + ] + ] + } } diff --git a/packages/core/android/.project b/packages/core/android/.project deleted file mode 100644 index 0e0a1bac2..000000000 --- a/packages/core/android/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - android_ - Project android_ created by Buildship. - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/packages/core/android/.settings/org.eclipse.buildship.core.prefs b/packages/core/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index 8c253d679..000000000 --- a/packages/core/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,13 +0,0 @@ -arguments= -auto.sync=false -build.scans.enabled=false -connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0)) -connection.project.dir= -eclipse.preferences.version=1 -gradle.user.home= -java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home -jvm.arguments= -offline.mode=false -override.workspace.settings=true -show.console.view=true -show.executions.view=true diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 43e5b8bcb..1b0ec556c 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -130,8 +130,8 @@ android { disable 'GradleCompatible' } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } } diff --git a/packages/core/android/gradle.properties b/packages/core/android/gradle.properties index 4f16df59e..ace79aead 100644 --- a/packages/core/android/gradle.properties +++ b/packages/core/android/gradle.properties @@ -1,5 +1,5 @@ DdSdkReactNative_kotlinVersion=1.7.21 -DdSdkReactNative_compileSdkVersion=31 -DdSdkReactNative_buildToolsVersion=31.0.0 -DdSdkReactNative_targetSdkVersion=31 +DdSdkReactNative_compileSdkVersion=33 +DdSdkReactNative_buildToolsVersion=33.0.0 +DdSdkReactNative_targetSdkVersion=33 android.useAndroidX=true diff --git a/packages/core/package.json b/packages/core/package.json index fa3b89ad6..33d6c1d84 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -55,14 +55,22 @@ }, "jest": { "preset": "react-native", + "moduleNameMapper": { + "@datadog/mobile-react-native": "../core/src" + }, "modulePathIgnorePatterns": [ "/lib/" ], + "testPathIgnorePatterns": [ + "/__utils__/" + ], "setupFiles": [ "./../../node_modules/react-native-gesture-handler/jestSetup.js", "./../../jest.setup.js" ], - "transformIgnorePatterns": [] + "transformIgnorePatterns": [ + "jest-runner" + ] }, "react-native-builder-bob": { "source": "src", diff --git a/packages/react-native-apollo-client/package.json b/packages/react-native-apollo-client/package.json index f12d7ef76..3fda94157 100644 --- a/packages/react-native-apollo-client/package.json +++ b/packages/react-native-apollo-client/package.json @@ -51,13 +51,18 @@ }, "jest": { "preset": "react-native", + "moduleNameMapper": { + "@datadog/mobile-react-native": "../core/src" + }, "modulePathIgnorePatterns": [ "/lib/" ], "testPathIgnorePatterns": [ "/__utils__/" ], - "transformIgnorePatterns": [] + "transformIgnorePatterns": [ + "jest-runner" + ] }, "react-native-builder-bob": { "source": "src", diff --git a/packages/react-native-navigation/package.json b/packages/react-native-navigation/package.json index e9b000ab4..ad35e6b26 100644 --- a/packages/react-native-navigation/package.json +++ b/packages/react-native-navigation/package.json @@ -1,76 +1,84 @@ { - "name": "@datadog/mobile-react-native-navigation", - "version": "1.8.5", - "description": "A client-side React Native module to interact with Datadog", - "keywords": [ - "datadog", - "react-native", - "ios", - "android" - ], - "author": "Datadog (https://github.com/DataDog)", - "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", - "repository": { - "url": "https://github.com/DataDog/dd-sdk-reactnative", - "directory": "packages/react-native-navigation" - }, - "bugs": { - "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" - }, - "license": "Apache-2.0", - "main": "lib/commonjs/index", - "files": [ - "src/**", - "lib/**" - ], - "types": "lib/typescript/react-native-navigation/src/index.d.ts", - "react-native": "src/index", - "source": "src/index", - "module": "lib/module/index", - "publishConfig": { - "access": "public" - }, - "scripts": { - "test": "jest", - "lint": "eslint .", - "prepare": "rm -rf lib && yarn bob build" - }, - "devDependencies": { - "@datadog/mobile-react-native": "^1.8.5", - "@testing-library/react-native": "7.0.2", - "react-native-builder-bob": "0.17.1", - "react-native-gesture-handler": "1.10.3", - "react-native-navigation": "7.31.1", - "remx": "3.x.x" - }, - "peerDependencies": { - "@datadog/mobile-react-native": "^1.0.0-beta4", - "react": ">=16.13.1", - "react-native": ">=0.63.4 <1.0", - "react-native-navigation": "^7.5.0" - }, - "jest": { - "preset": "react-native", - "modulePathIgnorePatterns": [ - "/lib/" + "name": "@datadog/mobile-react-native-navigation", + "version": "1.8.5", + "description": "A client-side React Native module to interact with Datadog", + "keywords": [ + "datadog", + "react-native", + "ios", + "android" ], - "setupFiles": [ - "./../../node_modules/react-native-gesture-handler/jestSetup.js" + "author": "Datadog (https://github.com/DataDog)", + "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", + "repository": { + "url": "https://github.com/DataDog/dd-sdk-reactnative", + "directory": "packages/react-native-navigation" + }, + "bugs": { + "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" + }, + "license": "Apache-2.0", + "main": "lib/commonjs/index", + "files": [ + "src/**", + "lib/**" ], - "transformIgnorePatterns": [] - }, - "react-native-builder-bob": { - "source": "src", - "output": "lib", - "targets": [ - "commonjs", - "module", - [ - "typescript", - { - "tsc": "./../../node_modules/.bin/tsc" - } - ] - ] - } + "types": "lib/typescript/react-native-navigation/src/index.d.ts", + "react-native": "src/index", + "source": "src/index", + "module": "lib/module/index", + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "lint": "eslint .", + "prepare": "rm -rf lib && yarn bob build" + }, + "devDependencies": { + "@datadog/mobile-react-native": "^1.8.5", + "@testing-library/react-native": "7.0.2", + "react-native-builder-bob": "0.17.1", + "react-native-gesture-handler": "1.10.3", + "react-native-navigation": "7.31.1", + "remx": "3.x.x" + }, + "peerDependencies": { + "@datadog/mobile-react-native": "^1.0.0-beta4", + "react": ">=16.13.1", + "react-native": ">=0.63.4 <1.0", + "react-native-navigation": "^7.5.0" + }, + "jest": { + "preset": "react-native", + "moduleNameMapper": { + "@datadog/mobile-react-native": "../core/src" + }, + "modulePathIgnorePatterns": [ + "/lib/" + ], + "setupFiles": [ + "./../../node_modules/react-native-gesture-handler/jestSetup.js" + ], + "testPathIgnorePatterns": [ + "/__utils__/" + ], + "transformIgnorePatterns": [ + "jest-runner" + ] + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "tsc": "./../../node_modules/.bin/tsc" + } + ] + ] + } } diff --git a/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec new file mode 100644 index 000000000..139a9148c --- /dev/null +++ b/packages/react-native-session-replay/DatadogSDKReactNativeSessionReplay.podspec @@ -0,0 +1,37 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +Pod::Spec.new do |s| + s.name = "DatadogSDKReactNativeSessionReplay" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => "11.0", :tvos => "11.0" } + s.source = { :git => "https://github.com/DataDog/dd-sdk-reactnative.git", :tag => "#{s.version}" } + + + s.source_files = "ios/Sources/*.{h,m,mm,swift}" + + s.dependency "React-Core" + + s.test_spec 'Tests' do |test_spec| + test_spec.source_files = 'ios/Tests/*.swift' + end + + + # This guard prevents installing the dependencies when we run `pod install` in the old architecture. + # The `install_modules_dependencies` function is only available from RN 0.71, the new architecture is not + # supported on earlier RN versions. + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + s.pod_target_xcconfig = { + "DEFINES_MODULE" => "YES", + "OTHER_CPLUSPLUSFLAGS" => "-DRCT_NEW_ARCH_ENABLED=1" + } + + install_modules_dependencies(s) + end +end diff --git a/packages/react-native-session-replay/README.md b/packages/react-native-session-replay/README.md new file mode 100644 index 000000000..12c771d63 --- /dev/null +++ b/packages/react-native-session-replay/README.md @@ -0,0 +1,3 @@ +# Session Replay for React Native + +Session Replay for React Native is not available yet. diff --git a/packages/react-native-session-replay/__mocks__/react-native.ts b/packages/react-native-session-replay/__mocks__/react-native.ts new file mode 100644 index 000000000..8780f1fff --- /dev/null +++ b/packages/react-native-session-replay/__mocks__/react-native.ts @@ -0,0 +1,18 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import type { NativeSessionReplayType } from '../src/nativeModulesTypes'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const actualRN = require('react-native'); + +actualRN.NativeModules.DdSessionReplay = { + enable: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction +}; + +module.exports = actualRN; diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle new file mode 100644 index 000000000..14bc0dae7 --- /dev/null +++ b/packages/react-native-session-replay/android/build.gradle @@ -0,0 +1,222 @@ +buildscript { + // Buildscript is evaluated before everything else so we can't use getExtOrDefault + def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['DatadogSDKReactNativeSessionReplay_kotlinVersion'] + + repositories { + mavenCentral() + google() + gradlePluginPortal() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.2' + // noinspection DifferentKotlinGradleVersion + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jlleitschuh.gradle:ktlint-gradle:11.5.1" + classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.18.0" + classpath 'com.github.bjoernq:unmockplugin:0.7.9' + } +} + + +apply plugin: 'de.mobilej.unmock' + +def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} + +apply plugin: 'com.android.library' +if (isNewArchitectureEnabled()) { + apply plugin: 'com.facebook.react' +} +apply plugin: 'kotlin-android' +apply plugin: 'org.jlleitschuh.gradle.ktlint' +apply plugin: "io.gitlab.arturbosch.detekt" + +def resolveReactNativeDirectory() { + def reactNativeLocation = rootProject.hasProperty("reactNativeDir") ? rootProject.getProperty("reactNativeDir") : null + + if (reactNativeLocation != null) { + return file(reactNativeLocation) + } + + try { + // Resolve React Native location with Node + // This will make sure that we get installation location correctly in monorepos + def reactNativePackageJsonPathStdout = new ByteArrayOutputStream() + + exec { + commandLine("node", "-p", "require.resolve('react-native/package.json')") + ignoreExitValue true + standardOutput = reactNativePackageJsonPathStdout + } + + def reactNativeFromProjectNodeModules = file(reactNativePackageJsonPathStdout.toString().trim()).getParentFile(); + + if (reactNativeFromProjectNodeModules.exists()) { + return reactNativeFromProjectNodeModules + } + } catch (e) { + // Ignore + } + + throw new Exception( + "${project.name}: Failed to resolve 'react-native' in the project. " + + "Altenatively, you can specify 'reactNativeDir' with the path to 'react-native' in your 'gradle.properties' file." + ) +} + +def reactNativeRootDir = resolveReactNativeDirectory() +def reactProperties = new Properties() +file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } + +def reactNativeVersion = reactProperties.getProperty("VERSION_NAME") +def (reactNativeMajorVersion, reactNativeMinorVersion) = reactNativeVersion.split("\\.").collect { it.isInteger() ? it.toInteger() : it } + +def getExtOrDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['DatadogSDKReactNativeSessionReplay_' + name] +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['DatadogSDKReactNativeSessionReplay_' + name]).toInteger() +} + +android { + compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') + buildToolsVersion getExtOrDefault('buildToolsVersion') + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + if (agpVersion.tokenize('.')[0].toInteger() >= 7) { + namespace = "com.datadog.reactnative.sessionreplay" + } + if (agpVersion.tokenize('.')[0].toInteger() >= 8) { + buildFeatures { + buildConfig = true + } + } + + defaultConfig { + minSdkVersion 21 + targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') + versionCode 1 + versionName "1.0" + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) + } + + sourceSets { + main { + if (isNewArchitectureEnabled()) { + java.srcDirs += ['src/newarch/kotlin'] + } else { + java.srcDirs += ['src/oldarch/kotlin'] + } + } + test { + java.srcDir("src/test/kotlin") + } + } + + testOptions { + unitTests { + returnDefaultValues = true + } + } + + buildTypes { + release { + minifyEnabled false + } + } + lintOptions { + disable 'GradleCompatible' + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } +} + +repositories { + mavenCentral() + google() + maven { url "https://jitpack.io" } + mavenLocal() + + if (reactNativeMajorVersion == 0 && reactNativeMinorVersion < 71) { + def androidSourcesDir = file("$reactNativeRootDir/android") + def androidSourcesName = "React Native sources" + + if (androidSourcesDir.exists()) { + maven { + url androidSourcesDir.toString() + name androidSourcesName + } + } + } +} + +def kotlin_version = getExtOrDefault('kotlinVersion') + +dependencies { + if (reactNativeMajorVersion == 0 && reactNativeMinorVersion < 71) { + // noinspection GradleDynamicVersion + api 'com.facebook.react:react-native:+' + } else { + // We specify the $reactNativeVersion, like it's done on the react-native-gradle-plugin, as the plugin is not applied in tests. + // There is no impact for apps as we apply the same logic: + // https://github.com/facebook/react-native/blob/e1a1e6aa8030bf11d691c3dcf7abd13b25175027/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt + api "com.facebook.react:react-android:$reactNativeVersion" + } + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + + testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" + testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" + testImplementation "org.junit.jupiter:junit-jupiter-engine:5.6.2" + testImplementation "org.junit.jupiter:junit-jupiter-params:5.6.2" + testImplementation "org.mockito:mockito-junit-jupiter:3.4.6" + testImplementation "org.assertj:assertj-core:3.18.1" + testImplementation "com.github.xgouchet.Elmyr:core:1.3.1" + testImplementation "com.github.xgouchet.Elmyr:inject:1.3.1" + testImplementation "com.github.xgouchet.Elmyr:junit5:1.3.1" + testImplementation "com.github.xgouchet.Elmyr:jvm:1.3.1" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + unmock 'org.robolectric:android-all:4.4_r1-robolectric-r2' +} + +tasks.withType(Test) { + useJUnitPlatform { + includeEngines("spek", "junit-jupiter", "junit-vintage") + } + reports { + junitXml.required.set(true) + html.required.set(true) + } +} + +tasks.named("check") { + dependsOn("ktlintCheck") + dependsOn("detekt") +} + +ktlint { + debug.set(false) + android.set(true) + outputToConsole.set(true) + ignoreFailures.set(false) + enableExperimentalRules.set(false) + filter { + exclude("**/generated/**") + include("**/kotlin/**") + } +} + +detekt { + input = files("$projectDir/src/main/kotlin") + config = files("$projectDir/detekt.yml") + reports { + xml { + enabled = true + destination = file("build/reports/detekt.xml") + } + } +} diff --git a/packages/react-native-session-replay/android/detekt.yml b/packages/react-native-session-replay/android/detekt.yml new file mode 100644 index 000000000..c1376f3bf --- /dev/null +++ b/packages/react-native-session-replay/android/detekt.yml @@ -0,0 +1,572 @@ +build: + maxIssues: 0 + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +processors: + active: true + exclude: + # - 'DetektProgressListener' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ClassCountProcessor' + # - 'PackageCountProcessor' + # - 'KtFileCountProcessor' + +console-reports: + active: true + exclude: + # - 'ProjectStatisticsReport' + # - 'ComplexityReport' + # - 'NotificationReport' + # - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'BuildFailureReport' + +comments: + active: true + CommentOverPrivateFunction: + active: true + CommentOverPrivateProperty: + active: true + EndOfSentenceFormat: + active: true + endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$) + UndocumentedPublicClass: + active: true + excludes: [ "**/test/**","**/androidTest/**","**/*.Test.kt","**/*.Spec.kt","**/*.Spek.kt" ] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: true + excludes: [ "**/test/**","**/androidTest/**","**/*.Test.kt","**/*.Spec.kt","**/*.Spek.kt" ] + UndocumentedPublicProperty: + active: true + excludes: [ "**/test/**","**/androidTest/**","**/*.Test.kt","**/*.Spec.kt","**/*.Spek.kt" ] + +complexity: + active: true + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: true + threshold: 10 + includeStaticDeclarations: false + ComplexMethod: + active: true + threshold: 10 + ignoreSingleWhenExpression: true + ignoreSimpleWhenEntries: true + LabeledExpression: + active: true + ignoredLabels: "" + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + threshold: 6 + ignoreDefaultParameters: true + MethodOverloading: + active: true + threshold: 6 + NestedBlockDepth: + active: true + threshold: 4 + StringLiteralDuplication: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: true + ignorePrivate: true + ignoreOverridden: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: true + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: 'toString,hashCode,equals,finalize' + InstanceOfCheckForException: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + NotImplementedDeclaration: + active: true + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: true + SwallowedException: + active: true + ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: true + ThrowingExceptionsWithoutMessageOrCause: + active: true + exceptions: 'IllegalArgumentException,IllegalStateException,IOException' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + exceptionNames: + - ArrayIndexOutOfBoundsException + - Error + - Exception + - IllegalMonitorStateException + - NullPointerException + - IndexOutOfBoundsException + - RuntimeException + - Throwable + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + TooGenericExceptionThrown: + active: true + exceptionNames: + - Error + - Exception + - Throwable + - RuntimeException + +formatting: + active: false + android: false + autoCorrect: true + AnnotationOnSeparateLine: + active: true + autoCorrect: true + ChainWrapping: + active: true + autoCorrect: true + CommentSpacing: + active: true + autoCorrect: true + Filename: + active: true + FinalNewline: + active: true + autoCorrect: true + ImportOrdering: + active: true + autoCorrect: true + Indentation: + active: true + autoCorrect: true + indentSize: 4 + continuationIndentSize: 4 + MaximumLineLength: + active: true + maxLineLength: 120 + ModifierOrdering: + active: true + autoCorrect: true + MultiLineIfElse: + active: true + autoCorrect: true + NoBlankLineBeforeRbrace: + active: true + autoCorrect: true + NoConsecutiveBlankLines: + active: true + autoCorrect: true + NoEmptyClassBody: + active: true + autoCorrect: true + NoLineBreakAfterElse: + active: true + autoCorrect: true + NoLineBreakBeforeAssignment: + active: true + autoCorrect: true + NoMultipleSpaces: + active: true + autoCorrect: true + NoSemicolons: + active: true + autoCorrect: true + NoTrailingSpaces: + active: true + autoCorrect: true + NoUnitReturn: + active: true + autoCorrect: true + NoUnusedImports: + active: true + autoCorrect: true + NoWildcardImports: + active: true + autoCorrect: true + PackageName: + active: true + autoCorrect: true + ParameterListWrapping: + active: true + autoCorrect: true + indentSize: 4 + SpacingAroundColon: + active: true + autoCorrect: true + SpacingAroundComma: + active: true + autoCorrect: true + SpacingAroundCurly: + active: true + autoCorrect: true + SpacingAroundDot: + active: true + autoCorrect: true + SpacingAroundKeyword: + active: true + autoCorrect: true + SpacingAroundOperators: + active: true + autoCorrect: true + SpacingAroundParens: + active: true + autoCorrect: true + SpacingAroundRangeOperator: + active: true + autoCorrect: true + StringTemplate: + active: true + autoCorrect: true + +naming: + active: true + ClassNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + classPattern: '[A-Z$][a-zA-Z0-9$]*' + ConstructorParameterNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + forbiddenName: '' + FunctionMaxLength: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + maximumFunctionNameLength: 30 + FunctionMinLength: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' + excludeClassPattern: '$^' + ignoreOverridden: true + FunctionParameterNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + InvalidPackageDeclaration: + active: true + rootPackage: '' + MatchingDeclarationName: + active: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + ObjectPropertyNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' + TopLevelPropertyNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + maximumVariableNameLength: 64 + VariableMinLength: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + minimumVariableNameLength: 1 + VariableNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + +performance: + active: true + ArrayPrimitive: + active: true + ForEachOnRange: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + SpreadOperator: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + Deprecation: + active: true + DuplicateCaseInWhenExpression: + active: true + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludeAnnotatedProperties: "" + ignoreOnClassesPattern: "" + MissingWhenCase: + active: true + RedundantElseInWhen: + active: true + UnconditionalJumpStatementInLoop: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + UnsafeCast: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: false + CollapsibleIfStatements: + active: true + DataClassContainsFunctions: + active: true + conversionFunctionPrefix: 'to' + DataClassShouldBeImmutable: + active: true + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: true + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenComment: + active: true + values: 'TODO:,FIXME:,STOPSHIP:' + allowedPatterns: "" + ForbiddenImport: + active: true + imports: '' + forbiddenPatterns: "" + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: true + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + excludedFunctions: 'describeContents' + excludeAnnotatedFunction: "dagger.Provides" + LibraryCodeMustSpecifyReturnType: + active: true + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + ignoreNumbers: '-1,0,1,2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + MandatoryBracesIfStatements: + active: true + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + MayBeConst: + active: true + ModifierOrder: + active: true + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: false + NoTabs: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: true + OptionalWhenBraces: + active: true + PreferToOverPairSyntax: + active: true + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: true + RedundantVisibilityModifierRule: + active: true + ReturnCount: + active: true + max: 2 + excludedFunctions: "equals" + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: true + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: true + ThrowsCount: + active: true + max: 2 + TrailingWhitespace: + active: true + UnderscoresInNumericLiterals: + active: true + acceptableDecimalLength: 5 + UnnecessaryAbstractClass: + active: true + excludeAnnotatedClasses: "dagger.Module" + UnnecessaryApply: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryLet: + active: true + UnnecessaryParentheses: + active: true + UntilInsteadOfRangeTo: + active: true + UnusedImports: + active: true + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: "(_|ignored|expected|serialVersionUID)" + UseArrayLiteralsInAnnotations: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: true + excludeAnnotatedClasses: "" + allowVars: true + UseIfInsteadOfWhen: + active: true + UseRequire: + active: true + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + WildcardImport: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludeImports: 'java.util.*,kotlinx.android.synthetic.*' diff --git a/packages/react-native-session-replay/android/gradle.properties b/packages/react-native-session-replay/android/gradle.properties new file mode 100644 index 000000000..397e8a6d4 --- /dev/null +++ b/packages/react-native-session-replay/android/gradle.properties @@ -0,0 +1,5 @@ +DatadogSDKReactNativeSessionReplay_kotlinVersion=1.7.21 +DatadogSDKReactNativeSessionReplay_compileSdkVersion=33 +DatadogSDKReactNativeSessionReplay_buildToolsVersion=33.0.0 +DatadogSDKReactNativeSessionReplay_targetSdkVersion=33 +android.useAndroidX=true diff --git a/packages/react-native-session-replay/android/gradle/wrapper/gradle-wrapper.jar b/packages/react-native-session-replay/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..62d4c0535 Binary files /dev/null and b/packages/react-native-session-replay/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/packages/react-native-session-replay/android/gradle/wrapper/gradle-wrapper.properties b/packages/react-native-session-replay/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..8cb39cca3 --- /dev/null +++ b/packages/react-native-session-replay/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Nov 20 08:39:09 CET 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/react-native-session-replay/android/gradlew b/packages/react-native-session-replay/android/gradlew new file mode 100755 index 000000000..fbd7c5158 --- /dev/null +++ b/packages/react-native-session-replay/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/packages/react-native-session-replay/android/gradlew.bat b/packages/react-native-session-replay/android/gradlew.bat new file mode 100644 index 000000000..5093609d5 --- /dev/null +++ b/packages/react-native-session-replay/android/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/react-native-session-replay/android/src/main/AndroidManifest.xml b/packages/react-native-session-replay/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..50dc33795 --- /dev/null +++ b/packages/react-native-session-replay/android/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DatadogSDKReactNativeSessionReplayPackage.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DatadogSDKReactNativeSessionReplayPackage.kt new file mode 100644 index 000000000..b9264f3a6 --- /dev/null +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DatadogSDKReactNativeSessionReplayPackage.kt @@ -0,0 +1,46 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.reactnative.sessionreplay + +import com.facebook.react.TurboReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider + +/** + * Package of native dd-sdk-reactnative native modules. + */ +class DatadogSDKReactNativeSessionReplayPackage : TurboReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return when (name) { + DdSessionReplayImplementation.NAME -> DdSessionReplay(reactContext) + else -> null + } + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { + val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + val moduleInfos = listOf( + DdSessionReplayImplementation.NAME + ).associateWith { + ReactModuleInfo( + it, + it, + false, // canOverrideExistingModule + false, // needsEagerInit + true, // hasConstants + false, // isCxxModule + isTurboModule // isTurboModule + ) + } + + moduleInfos + } + } +} diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt new file mode 100644 index 000000000..8dbd4d217 --- /dev/null +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt @@ -0,0 +1,27 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.reactnative.sessionreplay + +import com.facebook.react.bridge.Promise + +/** + * The entry point to use Datadog's Session Replay feature. + */ +class DdSessionReplayImplementation() { + /** + * Enable session replay and start recording session. + * @param replaySampleRate The sample rate applied for session replay. + * @param defaultPrivacyLevel The privacy level used for replay. + */ + fun enable(replaySampleRate: Double, defaultPrivacyLevel: String, promise: Promise) { + promise.resolve(null) + } + + companion object { + internal const val NAME = "DdSessionReplay" + } +} diff --git a/packages/react-native-session-replay/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt b/packages/react-native-session-replay/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt new file mode 100644 index 000000000..170ab7323 --- /dev/null +++ b/packages/react-native-session-replay/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt @@ -0,0 +1,33 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.reactnative.sessionreplay + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactMethod + +/** + * The entry point to use Datadog's Session Replay feature. + */ +class DdSessionReplay( + reactContext: ReactApplicationContext +) : NativeDdSessionReplaySpec(reactContext) { + + private val implementation = DdSessionReplayImplementation() + + override fun getName(): String = DdSessionReplayImplementation.NAME + + /** + * Enable session replay and start recording session. + * @param replaySampleRate The sample rate applied for session replay. + * @param defaultPrivacyLevel The privacy level used for replay. + */ + @ReactMethod + override fun enable(replaySampleRate: Double, defaultPrivacyLevel: String, promise: Promise) { + implementation.enable(replaySampleRate, defaultPrivacyLevel, promise) + } +} diff --git a/packages/react-native-session-replay/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt b/packages/react-native-session-replay/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt new file mode 100644 index 000000000..2d0c11cf1 --- /dev/null +++ b/packages/react-native-session-replay/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt @@ -0,0 +1,34 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.reactnative.sessionreplay + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod + +/** + * The entry point to use Datadog's Session Replay feature. + */ +class DdSessionReplay( + reactContext: ReactApplicationContext +) : ReactContextBaseJavaModule(reactContext) { + + private val implementation = DdSessionReplayImplementation() + + override fun getName(): String = DdSessionReplayImplementation.NAME + + /** + * Enable session replay and start recording session. + * @param replaySampleRate The sample rate applied for session replay. + * @param defaultPrivacyLevel The privacy level used for replay. + */ + @ReactMethod + fun enable(replaySampleRate: Double, defaultPrivacyLevel: String, promise: Promise) { + implementation.enable(replaySampleRate, defaultPrivacyLevel, promise) + } +} diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt new file mode 100644 index 000000000..57ccb69e2 --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt @@ -0,0 +1,47 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.reactnative.sessionreplay + +import com.facebook.react.bridge.Promise +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +internal class DdSessionReplayImplementationTest { + + lateinit var testedSessionReplay: DdSessionReplayImplementation + + @Mock + lateinit var mockPromise: Promise + + @BeforeEach + fun `set up`() { + testedSessionReplay = DdSessionReplayImplementation() + } + + @AfterEach + fun `tear down`() { + } + + @Test + fun `M do nothing W enable()`() { + // When + testedSessionReplay.enable(100.0, "MASK", mockPromise) + } +} diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt new file mode 100644 index 000000000..71b045169 --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt @@ -0,0 +1,69 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.unit + +import org.assertj.core.api.AbstractAssert +import org.assertj.core.api.Assertions.assertThat + +class GenericAssert(actual: Any?) : + AbstractAssert(actual, GenericAssert::class.java) { + + fun doesNotHaveField(name: String): GenericAssert { + val field: Any? = actual.getFieldValue(name) + assertThat(field) + .overridingErrorMessage( + "Expecting object to not have $name, but found it having value $field" + ) + .isNull() + return this + } + + fun getActualValue(name: String): T { + val field: Any? = actual.getFieldValue(name) + assertThat(field) + .overridingErrorMessage( + "Expecting object to have a non null field named $name, but field was null" + ) + .isNotNull() + return field!! as T + } + + fun hasField(name: String, nestedAssert: (GenericAssert) -> Unit = {}): GenericAssert { + val field: Any? = actual.getFieldValue(name) + assertThat(field) + .overridingErrorMessage( + "Expecting object to have a non null field named $name, but field was null" + ) + .isNotNull() + nestedAssert(GenericAssert(field!!)) + return this + } + + fun hasFieldEqualTo(name: String, expected: F): GenericAssert { + val field: Any? = actual.getFieldValue(name) + assertThat(field).isEqualTo(expected) + return this + } + + fun hasFieldWithClass(name: String, expectedClassName: String): GenericAssert { + val field: Any? = actual.getFieldValue(name) + assertThat(field?.javaClass?.name).isEqualTo(expectedClassName) + return this + } + + fun isInstanceOf(expectedClassName: String): GenericAssert { + val className = actual.javaClass.canonicalName!! + assertThat(className).isEqualTo(expectedClassName) + return this + } + + companion object { + fun assertThat(actual: Any?): GenericAssert { + return GenericAssert(actual) + } + } +} diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/MapExt.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/MapExt.kt new file mode 100644 index 000000000..6d6d4ce3a --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/MapExt.kt @@ -0,0 +1,29 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.unit + +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap + +fun Map<*, *>.toReadableMap(): ReadableMap { + val keysAndValues = mutableListOf() + + entries.forEach { + keysAndValues.add(it.key) + keysAndValues.add(it.value) + } + + // this FB implementation is not backed by Android-specific .so library, so ok for unit tests + return JavaOnlyMap.of(*keysAndValues.toTypedArray()) +} + +fun List<*>.toReadableArray(): ReadableArray { + // this FB implementation is not backed by Android-specific .so library, so ok for unit tests + return JavaOnlyArray.from(this) +} diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/ReflectUtils.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/ReflectUtils.kt new file mode 100644 index 000000000..49beb4465 --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/ReflectUtils.kt @@ -0,0 +1,266 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.unit + +import java.lang.reflect.Field +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.util.LinkedList +import kotlin.reflect.jvm.isAccessible + +/** + * Creates an instance of the given class name. + * @param className the full name of the class to instantiate + * @param params the parameters to provide the constructor + * @return the created instance + */ +@Suppress("SpreadOperator") +fun createInstance( + className: String, + vararg params: Any? +): Any { + return Class.forName(className) + .kotlin + .constructors.first() + .apply { isAccessible = true } + .call(*params) +} + +/** + * Sets a static value on the target class. + * @param fieldName the name of the field + * @param fieldValue the value to set + */ +@Suppress("SwallowedException") +inline fun Class.setStaticValue( + fieldName: String, + fieldValue: R +) { + val field = getDeclaredField(fieldName) + + // make it accessible + field.isAccessible = true + + // Make it non final + try { + val modifiersField = Field::class.java.getDeclaredField("modifiers") + modifiersField.isAccessible = true + modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv()) + } catch (e: NoSuchFieldException) { + // do nothing + @Suppress("PrintStackTrace") + e.printStackTrace() + } + field.set(null, fieldValue) +} + +/** + * Gets the static value from the target class. + * @param className the full name of the class + * @param fieldName the name of the field + */ +inline fun getStaticValue( + className: String, + fieldName: String +): R { + val clazz = Class.forName(className) + val field = clazz.getDeclaredField(fieldName) + // make it accessible + field.isAccessible = true + + return field.get(null) as R +} + +/** + * Gets the static value from the target class. + * @param fieldName the name of the field + */ +inline fun Class.getStaticValue(fieldName: String): R { + val field = getDeclaredField(fieldName) + + // make it accessible + field.isAccessible = true + + return field.get(null) as R +} + +/** + * Sets the field value on the target instance. + * @param fieldName the name of the field + * @param fieldValue the value of the field + */ +@Suppress("SwallowedException") +inline fun Any.setFieldValue( + fieldName: String, + fieldValue: T +): Boolean { + var field: Field? = null + val classesToSearch = LinkedList>() + classesToSearch.add(this.javaClass) + val classesSearched = mutableSetOf>() + + while (field == null && classesToSearch.isNotEmpty()) { + val toSearchIn = classesToSearch.remove() + try { + field = toSearchIn.getDeclaredField(fieldName) + } catch (e: NoSuchFieldException) { + // do nothing + } + classesSearched.add(toSearchIn) + toSearchIn.superclass?.let { + if (!classesSearched.contains(it)) { + classesToSearch.add(it) + } + } + } + + // make it accessible + if (field != null) { + field.isAccessible = true + + // Make it non final + val modifiersField = Field::class.java.getDeclaredField("modifiers") + modifiersField.isAccessible = true + modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv()) + + field.set(this, fieldValue) + return true + } else { + return false + } +} + +/** + * Gets the field value from the target instance. + * @param fieldName the name of the field + */ +inline fun R.getFieldValue( + fieldName: String, + enclosingClass: Class = this.javaClass +): T { + val field = enclosingClass.getDeclaredField(fieldName) + field.isAccessible = true + return field.get(this) as T +} + +/** + * Invokes a method on the target instance. + * @param methodName the name of the method + * @param params the parameters to provide the method + * @return the result from the invoked method + */ +@Suppress("SpreadOperator", "UNCHECKED_CAST", "TooGenericExceptionCaught") +fun T.invokeMethod( + methodName: String, + vararg params: Any? +): Any? { + val declarationParams = Array?>(params.size) { + params[it]?.javaClass + } + + val method = getDeclaredMethodRecursively(methodName, true, declarationParams) + val wasAccessible = method.isAccessible + + val output: Any? + method.isAccessible = true + try { + output = if (params.isEmpty()) { + method.invoke(this) + } else { + method.invoke(this, *params) + } + } catch (e: InvocationTargetException) { + throw e.cause ?: e + } finally { + method.isAccessible = wasAccessible + } + + return output +} + +/** + * Invokes a method on the target instance, where one or more of the parameters + * are generics. + * @param methodName the name of the method + * @param params the parameters to provide the method + * @return the result from the invoked method + */ +@Suppress("SpreadOperator", "UNCHECKED_CAST") +fun T.invokeGenericMethod( + methodName: String, + vararg params: Any +): Any? { + val declarationParams = Array?>(params.size) { + params[it].javaClass + } + + val method = getDeclaredMethodRecursively(methodName, false, declarationParams) + val wasAccessible = method.isAccessible + + val output: Any? + method.isAccessible = true + try { + output = if (params.isEmpty()) { + method.invoke(this) + } else { + method.invoke(this, *params) + } + } catch (e: InvocationTargetException) { + throw e.cause ?: e + } finally { + method.isAccessible = wasAccessible + } + + return output +} + +@Suppress("TooGenericExceptionCaught", "SwallowedException", "SpreadOperator") +private fun T.getDeclaredMethodRecursively( + methodName: String, + matchingParams: Boolean, + declarationParams: Array?> +): Method { + val classesToSearch = mutableListOf>(this.javaClass) + val classesSearched = mutableListOf>() + var method: Method? + do { + val lookingInClass = classesToSearch.removeAt(0) + classesSearched.add(lookingInClass) + method = try { + if (matchingParams) { + lookingInClass.getDeclaredMethod(methodName, *declarationParams) + } else { + lookingInClass.declaredMethods.firstOrNull { + it.name == methodName && + it.parameterTypes.size == declarationParams.size + } + } + } catch (e: Throwable) { + null + } + + val superclass = lookingInClass.superclass + if (superclass != null && + !classesToSearch.contains(superclass) && + !classesSearched.contains(superclass) + ) { + classesToSearch.add(superclass) + } + lookingInClass.interfaces.forEach { + if (!classesToSearch.contains(it) && !classesSearched.contains(it)) { + classesToSearch.add(it) + } + } + } while (method == null && classesToSearch.isNotEmpty()) + + checkNotNull(method) { + "Unable to access method $methodName on ${javaClass.canonicalName}" + } + + return method +} diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/BaseConfigurator.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/BaseConfigurator.kt new file mode 100644 index 000000000..53834d25a --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/BaseConfigurator.kt @@ -0,0 +1,24 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.unit.forge + +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeConfigurator +import fr.xgouchet.elmyr.jvm.useJvmFactories + +/** + * Base implementation of a [ForgeConfigurator], adding the JVM Forgery Factories (Date, …) and + * a [ThrowableForgeryFactory]. + */ +open class BaseConfigurator : + ForgeConfigurator { + /** @inheritDoc */ + override fun configure(forge: Forge) { + forge.addFactory(ThrowableForgeryFactory()) + forge.useJvmFactories() + } +} diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/Throwable.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/Throwable.kt new file mode 100644 index 000000000..fa8346685 --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/Throwable.kt @@ -0,0 +1,31 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.unit.forge + +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryException +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InvalidObjectException + +/** + * @return a random [Throwable] instance with a forged message. + */ +fun Forge.aThrowable(): Throwable { + val errorMessage = anAlphabeticalString() + return anElementFrom( + IOException(errorMessage), + IllegalStateException(errorMessage), + UnknownError(errorMessage), + ArrayIndexOutOfBoundsException(errorMessage), + NullPointerException(errorMessage), + ForgeryException(errorMessage), + InvalidObjectException(errorMessage), + UnsupportedOperationException(errorMessage), + FileNotFoundException(errorMessage) + ) +} diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ThrowableForgeryFactory.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ThrowableForgeryFactory.kt new file mode 100644 index 000000000..740fefdce --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ThrowableForgeryFactory.kt @@ -0,0 +1,21 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.unit.forge + +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +/** + * A [ForgeryFactory] generating a random [Throwable] instance with a forged message. + */ +class ThrowableForgeryFactory : + ForgeryFactory { + /** @inheritDoc */ + override fun getForgery(forge: Forge): Throwable { + return forge.aThrowable() + } +} diff --git a/packages/react-native-session-replay/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/packages/react-native-session-replay/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..1f0955d45 --- /dev/null +++ b/packages/react-native-session-replay/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/packages/react-native-session-replay/babel.config.js b/packages/react-native-session-replay/babel.config.js new file mode 100644 index 000000000..c50a8a001 --- /dev/null +++ b/packages/react-native-session-replay/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:metro-react-native-babel-preset'] +}; diff --git a/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.pbxproj b/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.pbxproj new file mode 100644 index 000000000..730d13c26 --- /dev/null +++ b/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.pbxproj @@ -0,0 +1,272 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814201AA4EA6300B7C361 /* libDatadogSDKReactNativeSessionReplay.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libDatadogSDKReactNativeSessionReplay.a; sourceTree = BUILT_PRODUCTS_DIR; }; + F625545A26A82D430033052D /* Sources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Sources; sourceTree = ""; }; + F625545B26A82D430033052D /* Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Tests; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libDatadogSDKReactNativeSessionReplay.a */, + ); + name = Products; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + F625545A26A82D430033052D /* Sources */, + F625545B26A82D430033052D /* Tests */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* DatadogSDKReactNativeSessionReplay */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "DatadogSDKReactNativeSessionReplay" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DatadogSDKReactNativeSessionReplay; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libDatadogSDKReactNativeSessionReplay.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "DatadogSDKReactNativeSessionReplay" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* DatadogSDKReactNativeSessionReplay */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = DatadogSDKReactNativeSessionReplay; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "DatadogSDKReactNativeSessionReplay-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = DatadogSDKReactNativeSessionReplay; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "DatadogSDKReactNativeSessionReplay-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "DatadogSDKReactNativeSessionReplay" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "DatadogSDKReactNativeSessionReplay" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..94b2795e2 --- /dev/null +++ b/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ + + + diff --git a/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/react-native-session-replay/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/react-native-session-replay/ios/Sources/DatadogSDKReactNativeSessionReplay.h b/packages/react-native-session-replay/ios/Sources/DatadogSDKReactNativeSessionReplay.h new file mode 100644 index 000000000..bb724670c --- /dev/null +++ b/packages/react-native-session-replay/ios/Sources/DatadogSDKReactNativeSessionReplay.h @@ -0,0 +1,8 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +// This file is imported in the auto-generated DatadogSDKReactNative-Swift.h header file. +// Deleting it could result in iOS builds failing. diff --git a/packages/react-native-session-replay/ios/Sources/DdSessionReplay.h b/packages/react-native-session-replay/ios/Sources/DdSessionReplay.h new file mode 100644 index 000000000..879194967 --- /dev/null +++ b/packages/react-native-session-replay/ios/Sources/DdSessionReplay.h @@ -0,0 +1,24 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +#import +@class DdSessionReplayImplementation; + +#ifdef RCT_NEW_ARCH_ENABLED + +#import +@interface DdSessionReplay: NSObject + +#else + +#import +@interface DdSessionReplay : NSObject + +#endif + +@property (nonatomic, strong) DdSessionReplayImplementation* ddSessionReplayImplementation; + +@end diff --git a/packages/react-native-session-replay/ios/Sources/DdSessionReplay.mm b/packages/react-native-session-replay/ios/Sources/DdSessionReplay.mm new file mode 100644 index 000000000..9f3a79b34 --- /dev/null +++ b/packages/react-native-session-replay/ios/Sources/DdSessionReplay.mm @@ -0,0 +1,52 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ +// Import this first to prevent require cycles +#if __has_include("DatadogSDKReactNativeSessionReplay-Swift.h") +#import +#else +#import +#endif +#import "DdSessionReplay.h" + + +@implementation DdSessionReplay + +RCT_EXPORT_MODULE() + +RCT_REMAP_METHOD(enable, withEnableReplaySampleRate:(double)replaySampleRate + withDefaultPrivacyLevel:(NSString*)defaultPrivacyLevel + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self enable:replaySampleRate defaultPrivacyLevel:defaultPrivacyLevel resolve:resolve reject:reject]; +} + +// Thanks to this guard, we won't compile this code when we build for the old architecture. +#ifdef RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} +#endif + +- (DdSessionReplayImplementation*)ddSessionReplayImplementation +{ + if (_ddSessionReplayImplementation == nil) { + _ddSessionReplayImplementation = [[DdSessionReplayImplementation alloc] init]; + } + return _ddSessionReplayImplementation; +} + ++ (BOOL)requiresMainQueueSetup { + return NO; +} + +- (void)enable:(double)replaySampleRate defaultPrivacyLevel:(NSString *)defaultPrivacyLevel resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddSessionReplayImplementation enableWithReplaySampleRate:replaySampleRate defaultPrivacyLevel:defaultPrivacyLevel resolve:resolve reject:reject]; +} + +@end diff --git a/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift b/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift new file mode 100644 index 000000000..fc1454342 --- /dev/null +++ b/packages/react-native-session-replay/ios/Sources/DdSessionReplayImplementation.swift @@ -0,0 +1,15 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import Foundation + +@objc +public class DdSessionReplayImplementation: NSObject { + @objc + public func enable(replaySampleRate: Double, defaultPrivacyLevel: String, resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + resolve(nil) + } +} diff --git a/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift new file mode 100644 index 000000000..5d85d7967 --- /dev/null +++ b/packages/react-native-session-replay/ios/Tests/DdSessionReplayTests.swift @@ -0,0 +1,18 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest + +internal class DdSessionReplayTests: XCTestCase { + private lazy var sessionReplay = DdSessionReplayImplementation() + + private func mockResolve(args: Any?) {} + private func mockReject(args: String?, arg: String?, err: Error?) {} + + func testDoesNothing() { + sessionReplay.enable(replaySampleRate: 100, defaultPrivacyLevel: "MASK", resolve: mockResolve, reject: mockReject) + } +} diff --git a/packages/react-native-session-replay/package.json b/packages/react-native-session-replay/package.json new file mode 100644 index 000000000..d85022d61 --- /dev/null +++ b/packages/react-native-session-replay/package.json @@ -0,0 +1,91 @@ +{ + "name": "@datadog/mobile-react-native-session-replay", + "version": "1.8.5", + "description": "A client-side React Native module to enable session replay with Datadog", + "keywords": [ + "datadog", + "react-native", + "ios", + "android" + ], + "author": "Datadog (https://github.com/DataDog)", + "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", + "repository": "https://github.com/DataDog/dd-sdk-reactnative", + "bugs": { + "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" + }, + "license": "Apache-2.0", + "main": "lib/commonjs/index", + "private": true, + "files": [ + "src/**", + "lib/**", + "android/build.gradle", + "android/detekt.yml", + "android/gradle.properties", + "android/src/**", + "ios/Sources/**", + "ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/xcsharedata", + "ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/*.xcworkspacedata", + "ios/DatadogSDKReactNativeSessionReplay.xcodeproj/*.pbxproj", + "DatadogSDKReactNativeSessionReplay.podspec" + ], + "types": "lib/typescript/index.d.ts", + "react-native": "src/index", + "source": "src", + "module": "lib/module/index", + "publishConfig": { + "access": "restricted" + }, + "scripts": { + "test": "jest", + "lint": "eslint .", + "prepare": "rm -rf lib && yarn bob build" + }, + "peerDependencies": { + "react": ">=16.13.1", + "react-native": ">=0.63.4 <1.0" + }, + "devDependencies": { + "@testing-library/react-native": "7.0.2", + "react-native-builder-bob": "0.17.1" + }, + "jest": { + "preset": "react-native", + "moduleNameMapper": { + "@datadog/mobile-react-native": "../core/src" + }, + "modulePathIgnorePatterns": [ + "/lib/" + ], + "setupFiles": [ + "./../../node_modules/react-native-gesture-handler/jestSetup.js", + "./../../jest.setup.js" + ], + "transformIgnorePatterns": [ + "jest-runner" + ] + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "tsc": "./../../node_modules/.bin/tsc" + } + ] + ] + }, + "codegenConfig": { + "name": "DatadogSDKReactNativeSessionReplay", + "type": "modules", + "jsSrcsDir": "./src/specs", + "android": { + "javaPackageName": "com.datadog.reactnative.sessionreplay" + } + } +} diff --git a/packages/react-native-session-replay/src/SessionReplay.ts b/packages/react-native-session-replay/src/SessionReplay.ts new file mode 100644 index 000000000..7eed3ea00 --- /dev/null +++ b/packages/react-native-session-replay/src/SessionReplay.ts @@ -0,0 +1,84 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import type { NativeSessionReplayType } from './nativeModulesTypes'; + +export enum SessionReplayPrivacy { + MASK = 'MASK', + ALLOW = 'ALLOW', + MASK_USER_INPUT = 'MASK_USER_INPUT' +} + +/** + * The Session Replay configuration object. + */ +export interface SessionReplayConfiguration { + /** + * The sampling rate for Session Replay. + * It is applied in addition to the RUM session sample rate. + * Range `0`-`100`. + * + * Default value is `20`. + */ + replaySampleRate?: number; + /** + * Defines the way sensitive content (e.g. text) should be masked. + * + * Default `SessionReplayPrivacy.MASK`. + */ + defaultPrivacyLevel?: SessionReplayPrivacy; +} + +const DEFAULTS = { + replaySampleRate: 20, + defaultPrivacyLevel: SessionReplayPrivacy.MASK +}; + +export class SessionReplayWrapper { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + private nativeSessionReplay: NativeSessionReplayType = require('./specs/NativeDdSessionReplay') + .default; + + private buildConfiguration = ( + configuration?: SessionReplayConfiguration + ): { + replaySampleRate: number; + defaultPrivacyLevel: SessionReplayPrivacy; + } => { + if (!configuration) { + return DEFAULTS; + } + const { replaySampleRate, defaultPrivacyLevel } = configuration; + return { + replaySampleRate: + replaySampleRate !== undefined + ? replaySampleRate + : DEFAULTS.replaySampleRate, + defaultPrivacyLevel: + defaultPrivacyLevel !== undefined + ? defaultPrivacyLevel + : DEFAULTS.defaultPrivacyLevel + }; + }; + + /** + * Enable session replay and start recording session. + * @param configuration: The session replay configuration. + */ + enable = (configuration?: SessionReplayConfiguration): Promise => { + const { + replaySampleRate, + defaultPrivacyLevel + } = this.buildConfiguration(configuration); + + return this.nativeSessionReplay.enable( + replaySampleRate, + defaultPrivacyLevel + ); + }; +} + +export const SessionReplay = new SessionReplayWrapper(); diff --git a/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts b/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts new file mode 100644 index 000000000..9adf6c885 --- /dev/null +++ b/packages/react-native-session-replay/src/__tests__/SessionReplay.test.ts @@ -0,0 +1,43 @@ +import { NativeModules } from 'react-native'; + +import { SessionReplay, SessionReplayPrivacy } from '../SessionReplay'; + +beforeEach(() => { + NativeModules.DdSessionReplay.enable.mockClear(); +}); + +describe('SessionReplay', () => { + describe('enable', () => { + it('calls native session replay with default configuration', () => { + SessionReplay.enable(); + + expect(NativeModules.DdSessionReplay.enable).toHaveBeenCalledWith( + 20, + 'MASK' + ); + }); + + it('calls native session replay with provided configuration', () => { + SessionReplay.enable({ + replaySampleRate: 100, + defaultPrivacyLevel: SessionReplayPrivacy.ALLOW + }); + + expect(NativeModules.DdSessionReplay.enable).toHaveBeenCalledWith( + 100, + 'ALLOW' + ); + }); + + it('calls native session replay with edge cases in configuration', () => { + SessionReplay.enable({ + replaySampleRate: 0 + }); + + expect(NativeModules.DdSessionReplay.enable).toHaveBeenCalledWith( + 0, + 'MASK' + ); + }); + }); +}); diff --git a/packages/react-native-session-replay/src/index.ts b/packages/react-native-session-replay/src/index.ts new file mode 100644 index 000000000..a44d6d529 --- /dev/null +++ b/packages/react-native-session-replay/src/index.ts @@ -0,0 +1,13 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import { + SessionReplay, + SessionReplayConfiguration, + SessionReplayPrivacy +} from './SessionReplay'; + +export { SessionReplay, SessionReplayConfiguration, SessionReplayPrivacy }; diff --git a/packages/react-native-session-replay/src/nativeModulesTypes.ts b/packages/react-native-session-replay/src/nativeModulesTypes.ts new file mode 100644 index 000000000..bbfd089f1 --- /dev/null +++ b/packages/react-native-session-replay/src/nativeModulesTypes.ts @@ -0,0 +1,29 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import type { Spec as NativeDdSessionReplay } from './specs/NativeDdSessionReplay'; + +/** + * In this file, native modules types extend the specs for TurboModules. + * As we cannot use enums or classes in the specs, we override methods using them here. + */ + +type PrivacyLevel = 'MASK' | 'MASK_USER_INPUT' | 'ALLOW'; + +/** + * The entry point to use Datadog's Session Replay feature. + */ +export interface NativeSessionReplayType extends NativeDdSessionReplay { + /** + * Enable session replay and start recording session. + * @param replaySampleRate: The sample rate applied for session replay. + * @param defaultPrivacyLevel: The privacy level used for replay. + */ + enable( + replaySampleRate: number, + defaultPrivacyLevel: PrivacyLevel + ): Promise; +} diff --git a/packages/react-native-session-replay/src/specs/NativeDdSessionReplay.ts b/packages/react-native-session-replay/src/specs/NativeDdSessionReplay.ts new file mode 100644 index 000000000..f557e4a5c --- /dev/null +++ b/packages/react-native-session-replay/src/specs/NativeDdSessionReplay.ts @@ -0,0 +1,28 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +/* eslint-disable @typescript-eslint/ban-types */ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +/** + * Do not import this Spec directly, use NativeSessionReplayType instead. + */ +export interface Spec extends TurboModule { + readonly getConstants: () => {}; + + /** + * Enable session replay and start recording session. + * @param replaySampleRate: The sample rate applied for session replay. + * @param defaultPrivacyLevel: The privacy level used for replay. + */ + enable( + replaySampleRate: number, + defaultPrivacyLevel: string + ): Promise; +} + +export default TurboModuleRegistry.get('DdSessionReplay'); diff --git a/packages/react-native-session-replay/tsconfig.json b/packages/react-native-session-replay/tsconfig.json new file mode 100644 index 000000000..41716a7dd --- /dev/null +++ b/packages/react-native-session-replay/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig" +} diff --git a/packages/react-native-webview/__mocks__/react-native.ts b/packages/react-native-webview/__mocks__/react-native.ts new file mode 100644 index 000000000..25a4ea88d --- /dev/null +++ b/packages/react-native-webview/__mocks__/react-native.ts @@ -0,0 +1,21 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +import type { PartialNativeDdSdkSpec } from '../src/NativeDdSdk'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const actualRN = require('react-native'); + +actualRN.NativeModules.DdSdk = { + telemetryError: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, + consumeWebviewEvent: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction +}; + +module.exports = actualRN; diff --git a/packages/react-native-webview/package.json b/packages/react-native-webview/package.json index e3fe41825..7858a567e 100644 --- a/packages/react-native-webview/package.json +++ b/packages/react-native-webview/package.json @@ -49,13 +49,18 @@ }, "jest": { "preset": "react-native", + "moduleNameMapper": { + "@datadog/mobile-react-native": "../core/src" + }, "modulePathIgnorePatterns": [ "/lib/" ], "testPathIgnorePatterns": [ "/__utils__/" ], - "transformIgnorePatterns": [] + "transformIgnorePatterns": [ + "jest-runner" + ] }, "react-native-builder-bob": { "source": "src", diff --git a/packages/react-native-webview/src/NativeDdSdk.ts b/packages/react-native-webview/src/NativeDdSdk.ts index 53ec26002..7ebc0e3b9 100644 --- a/packages/react-native-webview/src/NativeDdSdk.ts +++ b/packages/react-native-webview/src/NativeDdSdk.ts @@ -11,7 +11,7 @@ import { TurboModuleRegistry } from 'react-native'; * We have to redefine the spec for the Native SDK here to be able to use the new architecture. * We don't declare it in a spec file so we don't end up with a duplicate definition of the native module. */ -interface PartialNativeDdSdkSpec extends TurboModule { +export interface PartialNativeDdSdkSpec extends TurboModule { consumeWebviewEvent(message: string): Promise; telemetryError(message: string, stack: string, kind: string): Promise; } diff --git a/packages/react-navigation/package.json b/packages/react-navigation/package.json index 6c732623a..feec71004 100644 --- a/packages/react-navigation/package.json +++ b/packages/react-navigation/package.json @@ -1,81 +1,86 @@ { - "name": "@datadog/mobile-react-navigation", - "version": "1.8.5", - "description": "A client-side React Native module to interact with Datadog", - "keywords": [ - "datadog", - "react-native", - "ios", - "android" - ], - "author": "Datadog (https://github.com/DataDog)", - "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", - "repository": { - "url": "https://github.com/DataDog/dd-sdk-reactnative", - "directory": "packages/react-navigation" - }, - "bugs": { - "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" - }, - "license": "Apache-2.0", - "main": "lib/commonjs/index", - "files": [ - "src/**", - "lib/**" - ], - "types": "lib/typescript/react-navigation/src/index.d.ts", - "react-native": "src/index", - "source": "src/index", - "module": "lib/module/index", - "publishConfig": { - "access": "public" - }, - "scripts": { - "test": "jest", - "lint": "eslint .", - "prepare": "rm -rf lib && yarn bob build" - }, - "devDependencies": { - "@datadog/mobile-react-native": "^1.8.5", - "@react-navigation/native-v5": "npm:@react-navigation/native@5.9.8", - "@react-navigation/native-v6": "npm:@react-navigation/native@6.1.2", - "@react-navigation/stack-v5": "npm:@react-navigation/stack@5.14.2", - "@react-navigation/stack-v6": "npm:@react-navigation/stack@6.2.1", - "@testing-library/react-native": "7.0.2", - "react-native-builder-bob": "0.17.1", - "react-native-gesture-handler": "1.10.3", - "react-native-safe-area-context": "3.1.9" - }, - "peerDependencies": { - "@datadog/mobile-react-native": "^1.0.0-beta4", - "react": ">=16.13.1", - "react-native": ">=0.63.4 <1.0" - }, - "jest": { - "preset": "react-native", - "modulePathIgnorePatterns": [ - "/lib/" + "name": "@datadog/mobile-react-navigation", + "version": "1.8.5", + "description": "A client-side React Native module to interact with Datadog", + "keywords": [ + "datadog", + "react-native", + "ios", + "android" ], - "setupFiles": [ - "./../../node_modules/react-native-gesture-handler/jestSetup.js" + "author": "Datadog (https://github.com/DataDog)", + "homepage": "https://github.com/DataDog/dd-sdk-reactnative#readme", + "repository": { + "url": "https://github.com/DataDog/dd-sdk-reactnative", + "directory": "packages/react-navigation" + }, + "bugs": { + "url": "https://github.com/DataDog/dd-sdk-reactnative/issues" + }, + "license": "Apache-2.0", + "main": "lib/commonjs/index", + "files": [ + "src/**", + "lib/**" ], - "testPathIgnorePatterns": [ - "/__utils__/" - ], - "transformIgnorePatterns": [] - }, - "react-native-builder-bob": { - "source": "src", - "output": "lib", - "targets": [ - "commonjs", - "module", - [ - "typescript", - { - "tsc": "./../../node_modules/.bin/tsc" - } - ] - ] - } + "types": "lib/typescript/react-navigation/src/index.d.ts", + "react-native": "src/index", + "source": "src/index", + "module": "lib/module/index", + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "lint": "eslint .", + "prepare": "rm -rf lib && yarn bob build" + }, + "devDependencies": { + "@datadog/mobile-react-native": "^1.8.5", + "@react-navigation/native-v5": "npm:@react-navigation/native@5.9.8", + "@react-navigation/native-v6": "npm:@react-navigation/native@6.1.2", + "@react-navigation/stack-v5": "npm:@react-navigation/stack@5.14.2", + "@react-navigation/stack-v6": "npm:@react-navigation/stack@6.2.1", + "@testing-library/react-native": "7.0.2", + "react-native-builder-bob": "0.17.1", + "react-native-gesture-handler": "1.10.3", + "react-native-safe-area-context": "3.1.9" + }, + "peerDependencies": { + "@datadog/mobile-react-native": "^1.0.0-beta4", + "react": ">=16.13.1", + "react-native": ">=0.63.4 <1.0" + }, + "jest": { + "preset": "react-native", + "moduleNameMapper": { + "@datadog/mobile-react-native": "../core/src" + }, + "modulePathIgnorePatterns": [ + "/lib/" + ], + "setupFiles": [ + "./../../node_modules/react-native-gesture-handler/jestSetup.js" + ], + "testPathIgnorePatterns": [ + "/__utils__/" + ], + "transformIgnorePatterns": [ + "jest-runner" + ] + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "tsc": "./../../node_modules/.bin/tsc" + } + ] + ] + } } diff --git a/yarn.lock b/yarn.lock index 82986d4c4..9eee0efa8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3592,6 +3592,18 @@ __metadata: languageName: unknown linkType: soft +"@datadog/mobile-react-native-session-replay@workspace:packages/react-native-session-replay": + version: 0.0.0-use.local + resolution: "@datadog/mobile-react-native-session-replay@workspace:packages/react-native-session-replay" + dependencies: + "@testing-library/react-native": 7.0.2 + react-native-builder-bob: 0.17.1 + peerDependencies: + react: ">=16.13.1" + react-native: ">=0.63.4 <1.0" + languageName: unknown + linkType: soft + "@datadog/mobile-react-native-webview@workspace:packages/react-native-webview": version: 0.0.0-use.local resolution: "@datadog/mobile-react-native-webview@workspace:packages/react-native-webview"