@amplitude/plugin-session-replay-react-native 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +66 -0
- package/amplitude-plugin-session-replay-react-native.podspec +43 -0
- package/android/build.gradle +101 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/amplitude/pluginsessionreplayreactnative/PluginSessionReplayReactNativeModule.kt +78 -0
- package/android/src/main/java/com/amplitude/pluginsessionreplayreactnative/PluginSessionReplayReactNativePackage.kt +17 -0
- package/android/src/main/java/com/amplitude/pluginsessionreplayreactnative/PluginSessionReplayViewManager.kt +22 -0
- package/ios/ConsoleLogger.swift +49 -0
- package/ios/PluginSessionReplayReactNative-Bridging-Header.h +2 -0
- package/ios/PluginSessionReplayReactNative.mm +17 -0
- package/ios/PluginSessionReplayReactNative.swift +54 -0
- package/ios/RCTAmpMaskViewManager.m +29 -0
- package/lib/commonjs/AmpMaskView.js +10 -0
- package/lib/commonjs/AmpMaskView.js.map +1 -0
- package/lib/commonjs/index.js +20 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native-module.js +18 -0
- package/lib/commonjs/native-module.js.map +1 -0
- package/lib/commonjs/session-replay.js +59 -0
- package/lib/commonjs/session-replay.js.map +1 -0
- package/lib/commonjs/version.js +9 -0
- package/lib/commonjs/version.js.map +1 -0
- package/lib/module/AmpMaskView.js +3 -0
- package/lib/module/AmpMaskView.js.map +1 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native-module.js +11 -0
- package/lib/module/native-module.js.map +1 -0
- package/lib/module/session-replay.js +58 -0
- package/lib/module/session-replay.js.map +1 -0
- package/lib/module/version.js +2 -0
- package/lib/module/version.js.map +1 -0
- package/lib/typescript/AmpMaskView.d.ts +7 -0
- package/lib/typescript/AmpMaskView.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +3 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/native-module.d.ts +2 -0
- package/lib/typescript/native-module.d.ts.map +1 -0
- package/lib/typescript/session-replay.d.ts +12 -0
- package/lib/typescript/session-replay.d.ts.map +1 -0
- package/lib/typescript/version.d.ts +2 -0
- package/lib/typescript/version.d.ts.map +1 -0
- package/package.json +96 -0
- package/src/AmpMaskView.tsx +8 -0
- package/src/index.tsx +3 -0
- package/src/native-module.tsx +19 -0
- package/src/session-replay.ts +60 -0
- package/src/version.ts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Amplitude, Inc.
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @amplitude/plugin-session-replay-react-native
|
|
2
|
+
|
|
3
|
+
Amplitude Session Replay plugin for React Native
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @amplitude/plugin-session-replay-react-native
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
Add the session replay plugin to your Amplitude instance as follows
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import { SessionReplayPlugin } from '@amplitude/plugin-session-replay-react-native';
|
|
16
|
+
|
|
17
|
+
// ...
|
|
18
|
+
|
|
19
|
+
await init('YOUR_API_KEY').promise;
|
|
20
|
+
await add(new SessionReplayPlugin()).promise;
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Masking views
|
|
25
|
+
To maks certain views, add the `AmpMaskView` tag with the mask property `amp-mask` around the section to be masked
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { AmpMaskView } from '@amplitude/plugin-session-replay-react-native';
|
|
29
|
+
|
|
30
|
+
// ...
|
|
31
|
+
|
|
32
|
+
<AmpMaskView mask="amp-mask">
|
|
33
|
+
<Text
|
|
34
|
+
style={[
|
|
35
|
+
styles.sectionTitle,
|
|
36
|
+
{
|
|
37
|
+
color: isDarkMode ? Colors.white : Colors.black,
|
|
38
|
+
},
|
|
39
|
+
]}
|
|
40
|
+
>
|
|
41
|
+
{title}
|
|
42
|
+
</Text>
|
|
43
|
+
</AmpMaskView>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Unmasking views
|
|
47
|
+
To unmask views, add the `AmpMaskView` tag with the mask property `amp-unmask` around the section to be unmasked
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import { AmpMaskView } from '@amplitude/plugin-session-replay-react-native';
|
|
51
|
+
|
|
52
|
+
// ...
|
|
53
|
+
|
|
54
|
+
<AmpMaskView mask="amp-unmask">
|
|
55
|
+
<Text
|
|
56
|
+
style={[
|
|
57
|
+
styles.sectionTitle,
|
|
58
|
+
{
|
|
59
|
+
color: isDarkMode ? Colors.white : Colors.black,
|
|
60
|
+
},
|
|
61
|
+
]}
|
|
62
|
+
>
|
|
63
|
+
{title}
|
|
64
|
+
</Text>
|
|
65
|
+
</AmpMaskView>
|
|
66
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
|
5
|
+
|
|
6
|
+
Pod::Spec.new do |s|
|
|
7
|
+
s.name = "amplitude-plugin-session-replay-react-native"
|
|
8
|
+
s.version = package["version"]
|
|
9
|
+
s.summary = package["description"]
|
|
10
|
+
s.homepage = package["homepage"]
|
|
11
|
+
s.license = package["license"]
|
|
12
|
+
s.authors = package["author"]
|
|
13
|
+
|
|
14
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
15
|
+
s.source = { :git => "https://github.com/amplitude/Amplitude-TypeScript.git", :tag => "#{s.version}" }
|
|
16
|
+
|
|
17
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
|
+
|
|
19
|
+
s.dependency 'AmplitudeSessionReplay'
|
|
20
|
+
|
|
21
|
+
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
22
|
+
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
|
23
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
24
|
+
install_modules_dependencies(s)
|
|
25
|
+
else
|
|
26
|
+
s.dependency "React-Core"
|
|
27
|
+
|
|
28
|
+
# Don't install the dependencies when we run `pod install` in the old architecture.
|
|
29
|
+
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
|
30
|
+
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
31
|
+
s.pod_target_xcconfig = {
|
|
32
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
|
33
|
+
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
|
|
34
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
|
35
|
+
}
|
|
36
|
+
s.dependency "React-Codegen"
|
|
37
|
+
s.dependency "RCT-Folly"
|
|
38
|
+
s.dependency "RCTRequired"
|
|
39
|
+
s.dependency "RCTTypeSafety"
|
|
40
|
+
s.dependency "ReactCommon/turbomodule/core"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
|
|
3
|
+
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["PluginSessionReplayReactNative_kotlinVersion"]
|
|
4
|
+
|
|
5
|
+
repositories {
|
|
6
|
+
google()
|
|
7
|
+
mavenCentral()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
dependencies {
|
|
11
|
+
classpath "com.android.tools.build:gradle:7.2.1"
|
|
12
|
+
// noinspection DifferentKotlinGradleVersion
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def reactNativeArchitectures() {
|
|
18
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
19
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def isNewArchitectureEnabled() {
|
|
23
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
apply plugin: "com.android.library"
|
|
27
|
+
apply plugin: "kotlin-android"
|
|
28
|
+
|
|
29
|
+
if (isNewArchitectureEnabled()) {
|
|
30
|
+
apply plugin: "com.facebook.react"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def getExtOrDefault(name) {
|
|
34
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["PluginSessionReplayReactNative_" + name]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def getExtOrIntegerDefault(name) {
|
|
38
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["PluginSessionReplayReactNative_" + name]).toInteger()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def supportsNamespace() {
|
|
42
|
+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
43
|
+
def major = parsed[0].toInteger()
|
|
44
|
+
def minor = parsed[1].toInteger()
|
|
45
|
+
|
|
46
|
+
// Namespace support was added in 7.3.0
|
|
47
|
+
return (major == 7 && minor >= 3) || major >= 8
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
android {
|
|
51
|
+
if (supportsNamespace()) {
|
|
52
|
+
namespace "com.amplitude.pluginsessionreplayreactnative"
|
|
53
|
+
|
|
54
|
+
sourceSets {
|
|
55
|
+
main {
|
|
56
|
+
manifest.srcFile "src/main/AndroidManifestNew.xml"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
62
|
+
|
|
63
|
+
defaultConfig {
|
|
64
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
65
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
buildTypes {
|
|
70
|
+
release {
|
|
71
|
+
minifyEnabled false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
lintOptions {
|
|
76
|
+
disable "GradleCompatible"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
compileOptions {
|
|
80
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
81
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
repositories {
|
|
86
|
+
mavenCentral()
|
|
87
|
+
google()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
91
|
+
|
|
92
|
+
dependencies {
|
|
93
|
+
implementation("com.amplitude:session-replay-android:[0.15.2, 1.0.0]")
|
|
94
|
+
|
|
95
|
+
// For < 0.71, this will be from the local maven repo
|
|
96
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
97
|
+
//noinspection GradleDynamicVersion
|
|
98
|
+
implementation "com.facebook.react:react-native:+"
|
|
99
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
100
|
+
}
|
|
101
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
package com.amplitude.pluginsessionreplayreactnative
|
|
2
|
+
|
|
3
|
+
import com.amplitude.android.sessionreplay.SessionReplay
|
|
4
|
+
import com.amplitude.common.Logger
|
|
5
|
+
import com.amplitude.common.android.LogcatLogger
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
8
|
+
import com.facebook.react.bridge.ReactMethod
|
|
9
|
+
import com.facebook.react.bridge.Promise
|
|
10
|
+
import com.facebook.react.bridge.WritableMap
|
|
11
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
12
|
+
|
|
13
|
+
class PluginSessionReplayReactNativeModule(private val reactContext: ReactApplicationContext) :
|
|
14
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
15
|
+
private lateinit var sessionReplay: SessionReplay
|
|
16
|
+
|
|
17
|
+
override fun getName(): String {
|
|
18
|
+
return "PluginSessionReplayReactNative"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@ReactMethod
|
|
22
|
+
fun setup(apiKey: String, deviceId: String?, sessionId: Double) {
|
|
23
|
+
LogcatLogger.logger.logMode = Logger.LogMode.DEBUG
|
|
24
|
+
sessionReplay = SessionReplay(
|
|
25
|
+
apiKey,
|
|
26
|
+
reactContext.applicationContext,
|
|
27
|
+
deviceId ?: "",
|
|
28
|
+
sessionId.toLong(),
|
|
29
|
+
logger = LogcatLogger.logger,
|
|
30
|
+
sampleRate = 1.0,
|
|
31
|
+
enableRemoteConfig = false,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@ReactMethod
|
|
36
|
+
fun setSessionId(sessionId: Double) {
|
|
37
|
+
sessionReplay.setSessionId(sessionId.toLong())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@ReactMethod
|
|
41
|
+
fun getSessionId(promise: Promise) {
|
|
42
|
+
promise.resolve(sessionReplay.getSessionId().toDouble())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@ReactMethod
|
|
46
|
+
fun getSessionReplayProperties(promise: Promise) {
|
|
47
|
+
val properties: Map<String, Any> = sessionReplay.getSessionReplayProperties()
|
|
48
|
+
val map: WritableMap = WritableNativeMap()
|
|
49
|
+
for ((key, value) in properties) {
|
|
50
|
+
if (value is String) {
|
|
51
|
+
map.putString(key, value)
|
|
52
|
+
} else if (value is Int) {
|
|
53
|
+
map.putInt(key, value)
|
|
54
|
+
} else if (value is Long) {
|
|
55
|
+
map.putDouble(key, value.toDouble())
|
|
56
|
+
} else if (value is Double) {
|
|
57
|
+
map.putDouble(key, value)
|
|
58
|
+
} else if (value is Boolean) {
|
|
59
|
+
map.putBoolean(key, value)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
promise.resolve(map)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@ReactMethod
|
|
66
|
+
fun flush() {
|
|
67
|
+
sessionReplay.flush()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@ReactMethod
|
|
71
|
+
fun teardown() {
|
|
72
|
+
sessionReplay.shutdown()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun invalidate() {
|
|
76
|
+
sessionReplay.shutdown()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.amplitude.pluginsessionreplayreactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PluginSessionReplayReactNativePackage : ReactPackage {
|
|
10
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
11
|
+
return listOf(PluginSessionReplayReactNativeModule(reactContext))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
15
|
+
return listOf(PluginSessionReplayViewManager())
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.amplitude.pluginsessionreplayreactnative
|
|
2
|
+
|
|
3
|
+
import android.view.ViewGroup
|
|
4
|
+
import com.amplitude.android.sessionreplay.SessionReplay
|
|
5
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
|
+
import com.facebook.react.views.view.ReactViewManager
|
|
7
|
+
|
|
8
|
+
class PluginSessionReplayViewManager : ReactViewManager() {
|
|
9
|
+
|
|
10
|
+
override fun getName(): String {
|
|
11
|
+
return "RCTAmpMaskView"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@ReactProp(name = "mask")
|
|
15
|
+
fun setMask(view: ViewGroup, ampMask: String) {
|
|
16
|
+
when (ampMask) {
|
|
17
|
+
"amp-mask" -> SessionReplay.mask(view)
|
|
18
|
+
"amp-unmask" -> SessionReplay.unmask(view)
|
|
19
|
+
"amp-block" -> SessionReplay.block(view)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import os.log
|
|
3
|
+
import AmplitudeSessionReplay
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@objc(AMPLogLevel)
|
|
7
|
+
public enum LogLevelEnum: Int {
|
|
8
|
+
case OFF
|
|
9
|
+
case ERROR
|
|
10
|
+
case WARN
|
|
11
|
+
case LOG
|
|
12
|
+
case DEBUG
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public class ConsoleLogger: AmplitudeSessionReplay.Logger {
|
|
16
|
+
public typealias LogLevel = LogLevelEnum
|
|
17
|
+
|
|
18
|
+
public var logLevel: Int
|
|
19
|
+
private var logger: OSLog
|
|
20
|
+
|
|
21
|
+
public init(logLevel: Int = LogLevelEnum.OFF.rawValue) {
|
|
22
|
+
self.logLevel = logLevel
|
|
23
|
+
self.logger = OSLog(subsystem: "Amplitude", category: "Logging")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public func error(message: String) {
|
|
27
|
+
if logLevel >= LogLevel.ERROR.rawValue {
|
|
28
|
+
os_log("Error: %@", log: logger, type: .error, message)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public func warn(message: String) {
|
|
33
|
+
if logLevel >= LogLevel.WARN.rawValue {
|
|
34
|
+
os_log("Warn: %@", log: logger, type: .default, message)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public func log(message: String) {
|
|
39
|
+
if logLevel >= LogLevel.LOG.rawValue {
|
|
40
|
+
os_log("Log: %@", log: logger, type: .info, message)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public func debug(message: String) {
|
|
45
|
+
if logLevel >= LogLevel.DEBUG.rawValue {
|
|
46
|
+
os_log("Debug: %@", log: logger, type: .debug, message)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(PluginSessionReplayReactNative, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(setup:(NSString)apiKey deviceId:(NSString)deviceId sessionId:(nonnull NSNumber)sessionId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
|
6
|
+
|
|
7
|
+
RCT_EXTERN_METHOD(setSessionId:(nonnull NSNumber) resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
|
8
|
+
|
|
9
|
+
RCT_EXTERN_METHOD(getSessionId:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
|
10
|
+
|
|
11
|
+
RCT_EXTERN_METHOD(getSessionReplayProperties:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
|
|
13
|
+
RCT_EXTERN_METHOD(flush:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
|
14
|
+
|
|
15
|
+
RCT_EXTERN_METHOD(teardown:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
|
16
|
+
|
|
17
|
+
@end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import AmplitudeSessionReplay
|
|
3
|
+
|
|
4
|
+
@objc(PluginSessionReplayReactNative)
|
|
5
|
+
class PluginSessionReplayReactNative: NSObject {
|
|
6
|
+
|
|
7
|
+
var sessionReplay: SessionReplay!
|
|
8
|
+
|
|
9
|
+
@objc(setup:deviceId:sessionId:resolve:reject:)
|
|
10
|
+
func setup(_ apiKey: String, deviceId: String, sessionId: NSNumber, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
|
|
11
|
+
print("setup: \(apiKey) \(deviceId) \(sessionId)")
|
|
12
|
+
sessionReplay = SessionReplay(apiKey:apiKey,
|
|
13
|
+
deviceId: deviceId,
|
|
14
|
+
sessionId: sessionId.int64Value,
|
|
15
|
+
sampleRate: 1.0,
|
|
16
|
+
logger:ConsoleLogger(logLevel: LogLevelEnum.DEBUG.rawValue),
|
|
17
|
+
enableRemoteConfig: false)
|
|
18
|
+
sessionReplay.start()
|
|
19
|
+
resolve(nil)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@objc(setSessionId:resolve:reject:)
|
|
23
|
+
func setSessionId(_ sessionId: NSNumber, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
|
|
24
|
+
print("setSessionId: \(sessionId)")
|
|
25
|
+
sessionReplay.sessionId = sessionId.int64Value
|
|
26
|
+
resolve(nil)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@objc(getSessionId:reject:)
|
|
30
|
+
func getSessionId(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
31
|
+
print("getSessionId")
|
|
32
|
+
resolve(NSNumber(value:sessionReplay.sessionId))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@objc(getSessionReplayProperties:reject:)
|
|
36
|
+
func getSessionReplayProperties(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
37
|
+
print("getSessionReplayProperties")
|
|
38
|
+
resolve(sessionReplay.additionalEventProperties)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@objc(flush:reject:)
|
|
42
|
+
func flush(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
|
|
43
|
+
print("flush")
|
|
44
|
+
sessionReplay.flush()
|
|
45
|
+
resolve(nil)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@objc(teardown:reject:)
|
|
49
|
+
func teardown(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
|
|
50
|
+
print("teardown")
|
|
51
|
+
sessionReplay.stop()
|
|
52
|
+
resolve(nil)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
#import <React/RCTView.h>
|
|
3
|
+
@import AmplitudeSessionReplay;
|
|
4
|
+
|
|
5
|
+
@interface RCTAmpMaskViewManager : RCTViewManager
|
|
6
|
+
@end
|
|
7
|
+
|
|
8
|
+
@implementation RCTAmpMaskViewManager
|
|
9
|
+
|
|
10
|
+
RCT_EXPORT_MODULE(RCTAmpMaskView)
|
|
11
|
+
|
|
12
|
+
- (UIView *)view
|
|
13
|
+
{
|
|
14
|
+
return [[RCTView alloc] init];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
RCT_CUSTOM_VIEW_PROPERTY(mask, NSString, RCTView)
|
|
18
|
+
{
|
|
19
|
+
NSString* mask = [RCTConvert NSString:json];
|
|
20
|
+
if ([mask isEqualToString:@"amp-mask"]) {
|
|
21
|
+
view.amp_isBlocked = true;
|
|
22
|
+
} else if ([mask isEqualToString:@"amp-block"]) {
|
|
23
|
+
view.amp_isBlocked = true;
|
|
24
|
+
} else if ([mask isEqualToString:@"amp-unmask"]) {
|
|
25
|
+
view.amp_isBlocked = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.AmpMaskView = void 0;
|
|
7
|
+
var _reactNative = require("react-native");
|
|
8
|
+
const AmpMaskView = (0, _reactNative.requireNativeComponent)('RCTAmpMaskView');
|
|
9
|
+
exports.AmpMaskView = AmpMaskView;
|
|
10
|
+
//# sourceMappingURL=AmpMaskView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["AmpMaskView","requireNativeComponent"],"sourceRoot":"../../src","sources":["AmpMaskView.tsx"],"mappings":";;;;;;AAAA;AAMO,MAAMA,WAAW,GACtB,IAAAC,mCAAsB,EAAmB,gBAAgB,CAAC;AAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "AmpMaskView", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _AmpMaskView.AmpMaskView;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "SessionReplayPlugin", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _sessionReplay.SessionReplayPlugin;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
var _sessionReplay = require("./session-replay");
|
|
19
|
+
var _AmpMaskView = require("./AmpMaskView");
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;;;;;;;AAAA;AAEA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PluginSessionReplayReactNative = void 0;
|
|
7
|
+
var _reactNative = require("react-native");
|
|
8
|
+
const LINKING_ERROR = `The package '@amplitude/plugin-session-replay-react-native' doesn't seem to be linked. Make sure: \n\n` + _reactNative.Platform.select({
|
|
9
|
+
ios: "- You have run 'pod install'\n",
|
|
10
|
+
default: ''
|
|
11
|
+
}) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
|
|
12
|
+
const PluginSessionReplayReactNative = _reactNative.NativeModules.PluginSessionReplayReactNative ? _reactNative.NativeModules.PluginSessionReplayReactNative : new Proxy({}, {
|
|
13
|
+
get() {
|
|
14
|
+
throw new Error(LINKING_ERROR);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
exports.PluginSessionReplayReactNative = PluginSessionReplayReactNative;
|
|
18
|
+
//# sourceMappingURL=native-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["LINKING_ERROR","Platform","select","ios","default","PluginSessionReplayReactNative","NativeModules","Proxy","get","Error"],"sourceRoot":"../../src","sources":["native-module.tsx"],"mappings":";;;;;;AAAA;AAEA,MAAMA,aAAa,GAChB,wGAAuG,GACxGC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAE1B,MAAMC,8BAA8B,GACzCC,0BAAa,CAACD,8BAA8B,GACxCC,0BAAa,CAACD,8BAA8B,GAC5C,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAG,GAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CAAC,CACF;AAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.SessionReplayPlugin = void 0;
|
|
7
|
+
var _nativeModule = require("./native-module");
|
|
8
|
+
var _version = require("./version");
|
|
9
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
10
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
11
|
+
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
12
|
+
class SessionReplayPlugin {
|
|
13
|
+
// this.config is defined in setup() which will always be called first
|
|
14
|
+
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
_defineProperty(this, "name", '@amplitude/plugin-session-replay-react-native');
|
|
19
|
+
_defineProperty(this, "type", 'enrichment');
|
|
20
|
+
_defineProperty(this, "config", void 0);
|
|
21
|
+
} // empty default constructor
|
|
22
|
+
|
|
23
|
+
async setup(config, _) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
console.log(`Installing @amplitude/plugin-session-replay-react-native, version ${_version.VERSION}.`);
|
|
26
|
+
await _nativeModule.PluginSessionReplayReactNative.setup(config.apiKey, config.deviceId, config.sessionId);
|
|
27
|
+
}
|
|
28
|
+
async execute(event) {
|
|
29
|
+
// On event, synchronize the session id to the what's on the browserConfig (source of truth)
|
|
30
|
+
// Choosing not to read from event object here, concerned about offline/delayed events messing up the state stored
|
|
31
|
+
// in SR.
|
|
32
|
+
if (this.config.sessionId && this.config.sessionId !== (await _nativeModule.PluginSessionReplayReactNative.getSessionId())) {
|
|
33
|
+
await _nativeModule.PluginSessionReplayReactNative.setSessionId(this.config.sessionId);
|
|
34
|
+
}
|
|
35
|
+
// Treating config.sessionId as source of truth, if the event's session id doesn't match, the
|
|
36
|
+
// event is not of the current session (offline/late events). In that case, don't tag the events
|
|
37
|
+
if (this.config.sessionId && this.config.sessionId === event.session_id) {
|
|
38
|
+
const sessionRecordingProperties = await _nativeModule.PluginSessionReplayReactNative.getSessionReplayProperties();
|
|
39
|
+
event.event_properties = {
|
|
40
|
+
...event.event_properties,
|
|
41
|
+
...sessionRecordingProperties
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return Promise.resolve(event);
|
|
45
|
+
}
|
|
46
|
+
async teardown() {
|
|
47
|
+
await _nativeModule.PluginSessionReplayReactNative.teardown();
|
|
48
|
+
// the following are initialized in setup() which will always be called first
|
|
49
|
+
// here we reset them to null to prevent memory leaks
|
|
50
|
+
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
this.config = null;
|
|
53
|
+
}
|
|
54
|
+
async getSessionReplayProperties() {
|
|
55
|
+
return _nativeModule.PluginSessionReplayReactNative.getSessionReplayProperties();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.SessionReplayPlugin = SessionReplayPlugin;
|
|
59
|
+
//# sourceMappingURL=session-replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["SessionReplayPlugin","constructor","setup","config","_","console","log","VERSION","PluginSessionReplayReactNative","apiKey","deviceId","sessionId","execute","event","getSessionId","setSessionId","session_id","sessionRecordingProperties","getSessionReplayProperties","event_properties","Promise","resolve","teardown"],"sourceRoot":"../../src","sources":["session-replay.ts"],"mappings":";;;;;;AAOA;AACA;AAAoC;AAAA;AAAA;AAE7B,MAAMA,mBAAmB,CAAmE;EAGjG;;EAEA;;EAGAC,WAAW,GAAG;IAAA,8BAPP,+CAA+C;IAAA,8BAC/C,YAAY;IAAA;EAQnB,CAAC,CADC;;EAGF,MAAMC,KAAK,CAACC,MAAyB,EAAEC,CAAoB,EAAiB;IAC1E,IAAI,CAACD,MAAM,GAAGA,MAAM;IACpBE,OAAO,CAACC,GAAG,CAAE,qEAAoEC,gBAAQ,GAAE,CAAC;IAC5F,MAAMC,4CAA8B,CAACN,KAAK,CAACC,MAAM,CAACM,MAAM,EAAEN,MAAM,CAACO,QAAQ,EAAEP,MAAM,CAACQ,SAAS,CAAC;EAC9F;EAEA,MAAMC,OAAO,CAACC,KAAY,EAAyB;IACjD;IACA;IACA;IACA,IAAI,IAAI,CAACV,MAAM,CAACQ,SAAS,IAAI,IAAI,CAACR,MAAM,CAACQ,SAAS,MAAM,MAAMH,4CAA8B,CAACM,YAAY,EAAE,CAAC,EAAE;MAC5G,MAAMN,4CAA8B,CAACO,YAAY,CAAC,IAAI,CAACZ,MAAM,CAACQ,SAAS,CAAC;IAC1E;IACA;IACA;IACA,IAAI,IAAI,CAACR,MAAM,CAACQ,SAAS,IAAI,IAAI,CAACR,MAAM,CAACQ,SAAS,KAAKE,KAAK,CAACG,UAAU,EAAE;MACvE,MAAMC,0BAA0B,GAAG,MAAMT,4CAA8B,CAACU,0BAA0B,EAAE;MACpGL,KAAK,CAACM,gBAAgB,GAAG;QACvB,GAAGN,KAAK,CAACM,gBAAgB;QACzB,GAAGF;MACL,CAAC;IACH;IACA,OAAOG,OAAO,CAACC,OAAO,CAACR,KAAK,CAAC;EAC/B;EAEA,MAAMS,QAAQ,GAAkB;IAC9B,MAAMd,4CAA8B,CAACc,QAAQ,EAAE;IAC/C;IACA;;IAEA;IACA,IAAI,CAACnB,MAAM,GAAG,IAAI;EACpB;EAEA,MAAMe,0BAA0B,GAAG;IACjC,OAAOV,4CAA8B,CAACU,0BAA0B,EAAE;EACpE;AACF;AAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["VERSION"],"sourceRoot":"../../src","sources":["version.ts"],"mappings":";;;;;;AAAO,MAAMA,OAAO,GAAG,OAAO;AAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["requireNativeComponent","AmpMaskView"],"sourceRoot":"../../src","sources":["AmpMaskView.tsx"],"mappings":"AAAA,SAASA,sBAAsB,QAAwB,cAAc;AAMrE,OAAO,MAAMC,WAAW,GACtBD,sBAAsB,CAAmB,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["SessionReplayPlugin","AmpMaskView"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,SAASA,mBAAmB,QAAQ,kBAAkB;AAEtD,SAASC,WAAW,QAAQ,eAAe"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
const LINKING_ERROR = `The package '@amplitude/plugin-session-replay-react-native' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
|
|
3
|
+
ios: "- You have run 'pod install'\n",
|
|
4
|
+
default: ''
|
|
5
|
+
}) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
|
|
6
|
+
export const PluginSessionReplayReactNative = NativeModules.PluginSessionReplayReactNative ? NativeModules.PluginSessionReplayReactNative : new Proxy({}, {
|
|
7
|
+
get() {
|
|
8
|
+
throw new Error(LINKING_ERROR);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=native-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NativeModules","Platform","LINKING_ERROR","select","ios","default","PluginSessionReplayReactNative","Proxy","get","Error"],"sourceRoot":"../../src","sources":["native-module.tsx"],"mappings":"AAAA,SAASA,aAAa,EAAEC,QAAQ,QAAQ,cAAc;AAEtD,MAAMC,aAAa,GAChB,wGAAuG,GACxGD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,OAAO,MAAMC,8BAA8B,GACzCN,aAAa,CAACM,8BAA8B,GACxCN,aAAa,CAACM,8BAA8B,GAC5C,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAG,GAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CAAC,CACF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
2
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
3
|
+
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
7
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
9
|
+
|
|
10
|
+
import { PluginSessionReplayReactNative } from './native-module';
|
|
11
|
+
import { VERSION } from './version';
|
|
12
|
+
export class SessionReplayPlugin {
|
|
13
|
+
// this.config is defined in setup() which will always be called first
|
|
14
|
+
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
_defineProperty(this, "name", '@amplitude/plugin-session-replay-react-native');
|
|
19
|
+
_defineProperty(this, "type", 'enrichment');
|
|
20
|
+
_defineProperty(this, "config", void 0);
|
|
21
|
+
} // empty default constructor
|
|
22
|
+
|
|
23
|
+
async setup(config, _) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
console.log(`Installing @amplitude/plugin-session-replay-react-native, version ${VERSION}.`);
|
|
26
|
+
await PluginSessionReplayReactNative.setup(config.apiKey, config.deviceId, config.sessionId);
|
|
27
|
+
}
|
|
28
|
+
async execute(event) {
|
|
29
|
+
// On event, synchronize the session id to the what's on the browserConfig (source of truth)
|
|
30
|
+
// Choosing not to read from event object here, concerned about offline/delayed events messing up the state stored
|
|
31
|
+
// in SR.
|
|
32
|
+
if (this.config.sessionId && this.config.sessionId !== (await PluginSessionReplayReactNative.getSessionId())) {
|
|
33
|
+
await PluginSessionReplayReactNative.setSessionId(this.config.sessionId);
|
|
34
|
+
}
|
|
35
|
+
// Treating config.sessionId as source of truth, if the event's session id doesn't match, the
|
|
36
|
+
// event is not of the current session (offline/late events). In that case, don't tag the events
|
|
37
|
+
if (this.config.sessionId && this.config.sessionId === event.session_id) {
|
|
38
|
+
const sessionRecordingProperties = await PluginSessionReplayReactNative.getSessionReplayProperties();
|
|
39
|
+
event.event_properties = {
|
|
40
|
+
...event.event_properties,
|
|
41
|
+
...sessionRecordingProperties
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return Promise.resolve(event);
|
|
45
|
+
}
|
|
46
|
+
async teardown() {
|
|
47
|
+
await PluginSessionReplayReactNative.teardown();
|
|
48
|
+
// the following are initialized in setup() which will always be called first
|
|
49
|
+
// here we reset them to null to prevent memory leaks
|
|
50
|
+
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
this.config = null;
|
|
53
|
+
}
|
|
54
|
+
async getSessionReplayProperties() {
|
|
55
|
+
return PluginSessionReplayReactNative.getSessionReplayProperties();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=session-replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["PluginSessionReplayReactNative","VERSION","SessionReplayPlugin","constructor","setup","config","_","console","log","apiKey","deviceId","sessionId","execute","event","getSessionId","setSessionId","session_id","sessionRecordingProperties","getSessionReplayProperties","event_properties","Promise","resolve","teardown"],"sourceRoot":"../../src","sources":["session-replay.ts"],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;;AAGA,SAASA,8BAA8B,QAAQ,iBAAiB;AAChE,SAASC,OAAO,QAAQ,WAAW;AAEnC,OAAO,MAAMC,mBAAmB,CAAmE;EAGjG;;EAEA;;EAGAC,WAAW,GAAG;IAAA,8BAPP,+CAA+C;IAAA,8BAC/C,YAAY;IAAA;EAQnB,CAAC,CADC;;EAGF,MAAMC,KAAK,CAACC,MAAyB,EAAEC,CAAoB,EAAiB;IAC1E,IAAI,CAACD,MAAM,GAAGA,MAAM;IACpBE,OAAO,CAACC,GAAG,CAAE,qEAAoEP,OAAQ,GAAE,CAAC;IAC5F,MAAMD,8BAA8B,CAACI,KAAK,CAACC,MAAM,CAACI,MAAM,EAAEJ,MAAM,CAACK,QAAQ,EAAEL,MAAM,CAACM,SAAS,CAAC;EAC9F;EAEA,MAAMC,OAAO,CAACC,KAAY,EAAyB;IACjD;IACA;IACA;IACA,IAAI,IAAI,CAACR,MAAM,CAACM,SAAS,IAAI,IAAI,CAACN,MAAM,CAACM,SAAS,MAAM,MAAMX,8BAA8B,CAACc,YAAY,EAAE,CAAC,EAAE;MAC5G,MAAMd,8BAA8B,CAACe,YAAY,CAAC,IAAI,CAACV,MAAM,CAACM,SAAS,CAAC;IAC1E;IACA;IACA;IACA,IAAI,IAAI,CAACN,MAAM,CAACM,SAAS,IAAI,IAAI,CAACN,MAAM,CAACM,SAAS,KAAKE,KAAK,CAACG,UAAU,EAAE;MACvE,MAAMC,0BAA0B,GAAG,MAAMjB,8BAA8B,CAACkB,0BAA0B,EAAE;MACpGL,KAAK,CAACM,gBAAgB,GAAG;QACvB,GAAGN,KAAK,CAACM,gBAAgB;QACzB,GAAGF;MACL,CAAC;IACH;IACA,OAAOG,OAAO,CAACC,OAAO,CAACR,KAAK,CAAC;EAC/B;EAEA,MAAMS,QAAQ,GAAkB;IAC9B,MAAMtB,8BAA8B,CAACsB,QAAQ,EAAE;IAC/C;IACA;;IAEA;IACA,IAAI,CAACjB,MAAM,GAAG,IAAI;EACpB;EAEA,MAAMa,0BAA0B,GAAG;IACjC,OAAOlB,8BAA8B,CAACkB,0BAA0B,EAAE;EACpE;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["VERSION"],"sourceRoot":"../../src","sources":["version.ts"],"mappings":"AAAA,OAAO,MAAMA,OAAO,GAAG,OAAO"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ViewProps } from 'react-native';
|
|
2
|
+
interface AmpMaskViewProps extends ViewProps {
|
|
3
|
+
mask: 'amp-mask' | 'amp-unmask' | 'amp-block';
|
|
4
|
+
}
|
|
5
|
+
export declare const AmpMaskView: import("react-native").HostComponent<AmpMaskViewProps>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=AmpMaskView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AmpMaskView.d.ts","sourceRoot":"","sources":["../../src/AmpMaskView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEtE,UAAU,gBAAiB,SAAQ,SAAS;IAC1C,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,WAAW,CAAC;CAC/C;AAED,eAAO,MAAM,WAAW,wDACoC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-module.d.ts","sourceRoot":"","sources":["../../src/native-module.tsx"],"names":[],"mappings":"AAQA,eAAO,MAAM,8BAA8B,KAUpC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EnrichmentPlugin, Event, ReactNativeClient, ReactNativeConfig } from '@amplitude/analytics-types';
|
|
2
|
+
export declare class SessionReplayPlugin implements EnrichmentPlugin<ReactNativeClient, ReactNativeConfig> {
|
|
3
|
+
name: string;
|
|
4
|
+
type: "enrichment";
|
|
5
|
+
config: ReactNativeConfig;
|
|
6
|
+
constructor();
|
|
7
|
+
setup(config: ReactNativeConfig, _: ReactNativeClient): Promise<void>;
|
|
8
|
+
execute(event: Event): Promise<Event | null>;
|
|
9
|
+
teardown(): Promise<void>;
|
|
10
|
+
getSessionReplayProperties(): Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=session-replay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../src/session-replay.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAKhH,qBAAa,mBAAoB,YAAW,gBAAgB,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAChG,IAAI,SAAmD;IACvD,IAAI,eAAyB;IAI7B,MAAM,EAAE,iBAAiB,CAAC;;IAMpB,KAAK,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrE,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAmB5C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IASzB,0BAA0B;CAGjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amplitude/plugin-session-replay-react-native",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Amplitude Session Replay plugin for React Native",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"analytics",
|
|
7
|
+
"amplitude",
|
|
8
|
+
"react-native",
|
|
9
|
+
"ios",
|
|
10
|
+
"android",
|
|
11
|
+
"session-replay"
|
|
12
|
+
],
|
|
13
|
+
"author": "Amplitude Inc",
|
|
14
|
+
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"main": "lib/commonjs/index",
|
|
17
|
+
"module": "lib/module/index",
|
|
18
|
+
"types": "lib/typescript/index.d.ts",
|
|
19
|
+
"react-native": "src/index",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"tag": "latest"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/amplitude/Amplitude-TypeScript.git"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "bob build",
|
|
30
|
+
"clean": "rimraf node_modules lib coverage",
|
|
31
|
+
"fix": "yarn fix:eslint & yarn fix:prettier",
|
|
32
|
+
"fix:eslint": "eslint '{src,test}/**/*.ts' --fix",
|
|
33
|
+
"fix:prettier": "prettier --write \"{src,test}/**/*.ts\"",
|
|
34
|
+
"lint": "yarn lint:eslint & yarn lint:prettier",
|
|
35
|
+
"lint:eslint": "eslint '{src,test}/**/*.ts'",
|
|
36
|
+
"lint:prettier": "prettier --check \"{src,test}/**/*.ts\"",
|
|
37
|
+
"test": "jest",
|
|
38
|
+
"typecheck": "tsc -p ./tsconfig.json",
|
|
39
|
+
"version": "yarn version-file && yarn build",
|
|
40
|
+
"version-file": "node -p \"'export const VERSION = \\'' + require('./package.json').version + '\\';'\" > src/version.ts",
|
|
41
|
+
"typescript": "tsc --noEmit",
|
|
42
|
+
"example": "yarn workspace @amplitude/plugin-session-replay-react-native-example",
|
|
43
|
+
"pods": "cd example && pod-install --quiet"
|
|
44
|
+
},
|
|
45
|
+
"source": "src/index",
|
|
46
|
+
"files": [
|
|
47
|
+
"src",
|
|
48
|
+
"lib",
|
|
49
|
+
"android",
|
|
50
|
+
"ios",
|
|
51
|
+
"cpp",
|
|
52
|
+
"*.podspec",
|
|
53
|
+
"!ios/build",
|
|
54
|
+
"!android/build",
|
|
55
|
+
"!android/gradle",
|
|
56
|
+
"!android/gradlew",
|
|
57
|
+
"!android/gradlew.bat",
|
|
58
|
+
"!android/local.properties",
|
|
59
|
+
"!test",
|
|
60
|
+
"!**/__tests__",
|
|
61
|
+
"!**/__fixtures__",
|
|
62
|
+
"!**/__mocks__",
|
|
63
|
+
"!**/.*"
|
|
64
|
+
],
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@amplitude/analytics-types": "^1.3.4"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@types/react": "^18.0.26",
|
|
73
|
+
"react": "18.2.0",
|
|
74
|
+
"react-native": "0.73.0",
|
|
75
|
+
"react-native-builder-bob": "^0.20.3"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"react": "*",
|
|
79
|
+
"react-native": "*"
|
|
80
|
+
},
|
|
81
|
+
"react-native-builder-bob": {
|
|
82
|
+
"source": "src",
|
|
83
|
+
"output": "lib",
|
|
84
|
+
"targets": [
|
|
85
|
+
"commonjs",
|
|
86
|
+
"module",
|
|
87
|
+
[
|
|
88
|
+
"typescript",
|
|
89
|
+
{
|
|
90
|
+
"project": "tsconfig.build.json"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"gitHead": "2b09117a76f23d80de58d636e59911a2fda5d169"
|
|
96
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { requireNativeComponent, type ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
interface AmpMaskViewProps extends ViewProps {
|
|
4
|
+
mask: 'amp-mask' | 'amp-unmask' | 'amp-block';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const AmpMaskView =
|
|
8
|
+
requireNativeComponent<AmpMaskViewProps>('RCTAmpMaskView');
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
4
|
+
`The package '@amplitude/plugin-session-replay-react-native' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
+
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
|
+
'- You rebuilt the app after installing the package\n' +
|
|
7
|
+
'- You are not using Expo Go\n';
|
|
8
|
+
|
|
9
|
+
export const PluginSessionReplayReactNative =
|
|
10
|
+
NativeModules.PluginSessionReplayReactNative
|
|
11
|
+
? NativeModules.PluginSessionReplayReactNative
|
|
12
|
+
: new Proxy(
|
|
13
|
+
{},
|
|
14
|
+
{
|
|
15
|
+
get() {
|
|
16
|
+
throw new Error(LINKING_ERROR);
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
4
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
6
|
+
import type { EnrichmentPlugin, Event, ReactNativeClient, ReactNativeConfig } from '@amplitude/analytics-types';
|
|
7
|
+
|
|
8
|
+
import { PluginSessionReplayReactNative } from './native-module';
|
|
9
|
+
import { VERSION } from './version';
|
|
10
|
+
|
|
11
|
+
export class SessionReplayPlugin implements EnrichmentPlugin<ReactNativeClient, ReactNativeConfig> {
|
|
12
|
+
name = '@amplitude/plugin-session-replay-react-native';
|
|
13
|
+
type = 'enrichment' as const;
|
|
14
|
+
// this.config is defined in setup() which will always be called first
|
|
15
|
+
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
config: ReactNativeConfig;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
// empty default constructor
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async setup(config: ReactNativeConfig, _: ReactNativeClient): Promise<void> {
|
|
24
|
+
this.config = config;
|
|
25
|
+
console.log(`Installing @amplitude/plugin-session-replay-react-native, version ${VERSION}.`);
|
|
26
|
+
await PluginSessionReplayReactNative.setup(config.apiKey, config.deviceId, config.sessionId);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async execute(event: Event): Promise<Event | null> {
|
|
30
|
+
// On event, synchronize the session id to the what's on the browserConfig (source of truth)
|
|
31
|
+
// Choosing not to read from event object here, concerned about offline/delayed events messing up the state stored
|
|
32
|
+
// in SR.
|
|
33
|
+
if (this.config.sessionId && this.config.sessionId !== (await PluginSessionReplayReactNative.getSessionId())) {
|
|
34
|
+
await PluginSessionReplayReactNative.setSessionId(this.config.sessionId);
|
|
35
|
+
}
|
|
36
|
+
// Treating config.sessionId as source of truth, if the event's session id doesn't match, the
|
|
37
|
+
// event is not of the current session (offline/late events). In that case, don't tag the events
|
|
38
|
+
if (this.config.sessionId && this.config.sessionId === event.session_id) {
|
|
39
|
+
const sessionRecordingProperties = await PluginSessionReplayReactNative.getSessionReplayProperties();
|
|
40
|
+
event.event_properties = {
|
|
41
|
+
...event.event_properties,
|
|
42
|
+
...sessionRecordingProperties,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return Promise.resolve(event);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async teardown(): Promise<void> {
|
|
49
|
+
await PluginSessionReplayReactNative.teardown();
|
|
50
|
+
// the following are initialized in setup() which will always be called first
|
|
51
|
+
// here we reset them to null to prevent memory leaks
|
|
52
|
+
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
this.config = null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getSessionReplayProperties() {
|
|
58
|
+
return PluginSessionReplayReactNative.getSessionReplayProperties();
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '0.2.0';
|