@pulseboard/react-native 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "PulseBoardSDK"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = "https://github.com/yourusername/pulseboard"
10
+ s.license = package["license"]
11
+ s.authors = { "PulseBoard" => "hello@pulseboard.dev" }
12
+ s.platforms = { :ios => "15.1" }
13
+ s.source = { :git => "https://github.com/yourusername/pulseboard.git", :tag => "#{s.version}" }
14
+ s.source_files = "ios/**/*.{swift,h,m,mm}"
15
+ s.requires_arc = true
16
+
17
+ s.dependency "React-Core"
18
+ s.dependency "ReactCommon/turbomodule/core"
19
+
20
+ s.pod_target_xcconfig = {
21
+ "SWIFT_VERSION" => "5.9",
22
+ "CLANG_ENABLE_MODULES" => "YES",
23
+ }
24
+ end
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @pulseboard/react-native
2
+
3
+ > Official PulseBoard SDK for React Native. Built exclusively for iOS and Android using native React Native APIs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @pulseboard/react-native
9
+ ```
10
+
11
+ ## iOS
12
+
13
+ ```bash
14
+ npx pod-install
15
+ ```
16
+
17
+ ## Android Permissions
18
+
19
+ Add the following to your `AndroidManifest.xml`:
20
+
21
+ ```xml
22
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
23
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
24
+ ```
25
+
26
+ `READ_PHONE_STATE` is only required for carrier name detection.
27
+ If you prefer not to request it, carrier will return `"unknown"`.
@@ -0,0 +1,46 @@
1
+ buildscript {
2
+ ext.safeExtGet = { prop, fallback ->
3
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath("com.android.tools.build:gradle:8.1.0")
13
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
14
+ }
15
+ }
16
+
17
+ apply plugin: "com.android.library"
18
+ apply plugin: "kotlin-android"
19
+
20
+ android {
21
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
22
+ namespace "com.pulseboard"
23
+
24
+ defaultConfig {
25
+ minSdkVersion safeExtGet("minSdkVersion", 24)
26
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
27
+ }
28
+
29
+ compileOptions {
30
+ sourceCompatibility JavaVersion.VERSION_17
31
+ targetCompatibility JavaVersion.VERSION_17
32
+ }
33
+
34
+ kotlinOptions {
35
+ jvmTarget = "17"
36
+ }
37
+ }
38
+
39
+ repositories {
40
+ google()
41
+ mavenCentral()
42
+ }
43
+
44
+ dependencies {
45
+ implementation "com.facebook.react:react-android"
46
+ }
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
3
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
4
+ </manifest>
@@ -0,0 +1,81 @@
1
+ package com.pulseboard
2
+
3
+ import android.content.Context
4
+ import android.content.pm.PackageManager
5
+ import android.os.Build
6
+ import android.util.DisplayMetrics
7
+ import android.view.WindowManager
8
+ import com.facebook.react.bridge.*
9
+
10
+ class PulseBoardDeviceModule(private val reactContext: ReactApplicationContext) :
11
+ ReactContextBaseJavaModule(reactContext) {
12
+
13
+ override fun getName() = "PulseBoardDevice"
14
+
15
+ @ReactMethod
16
+ fun getDeviceInfo(promise: Promise) {
17
+ try {
18
+ val context = reactContext
19
+ val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
20
+ val metrics = DisplayMetrics()
21
+
22
+ @Suppress("DEPRECATION")
23
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
24
+
25
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
26
+ val windowMetrics = windowManager.currentWindowMetrics
27
+ metrics.widthPixels = windowMetrics.bounds.width()
28
+ metrics.heightPixels = windowMetrics.bounds.height()
29
+ } else {
30
+ @Suppress("DEPRECATION")
31
+ windowManager.defaultDisplay.getMetrics(metrics)
32
+ }
33
+
34
+ val result = Arguments.createMap().apply {
35
+ putString("model", Build.MODEL)
36
+ putString("manufacturer", Build.MANUFACTURER)
37
+ putString("brand", Build.BRAND)
38
+ putString("os", "Android")
39
+ putString("osVersion", Build.VERSION.RELEASE)
40
+ putString("appVersion", packageInfo.versionName ?: "unknown")
41
+ putString("buildNumber", getBuildNumber(packageInfo))
42
+ putString("bundleId", context.packageName)
43
+ putDouble("screenWidth", metrics.widthPixels.toDouble())
44
+ putDouble("screenHeight", metrics.heightPixels.toDouble())
45
+ putBoolean("isTablet", isTablet(context))
46
+ putBoolean("isEmulator", isEmulator())
47
+ }
48
+
49
+ promise.resolve(result)
50
+ } catch (e: Exception) {
51
+ promise.reject("DEVICE_INFO_ERROR", e.message, e)
52
+ }
53
+ }
54
+
55
+ private fun getBuildNumber(packageInfo: android.content.pm.PackageInfo): String {
56
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
57
+ packageInfo.longVersionCode.toString()
58
+ } else {
59
+ @Suppress("DEPRECATION")
60
+ packageInfo.versionCode.toString()
61
+ }
62
+ }
63
+
64
+ private fun isTablet(context: Context): Boolean {
65
+ val metrics = context.resources.displayMetrics
66
+ val widthDp = metrics.widthPixels / metrics.density
67
+ val heightDp = metrics.heightPixels / metrics.density
68
+ return minOf(widthDp, heightDp) >= 600f
69
+ }
70
+
71
+ private fun isEmulator(): Boolean {
72
+ return (Build.FINGERPRINT.startsWith("generic")
73
+ || Build.FINGERPRINT.startsWith("unknown")
74
+ || Build.MODEL.contains("Emulator")
75
+ || Build.MODEL.contains("Android SDK built for x86")
76
+ || Build.MANUFACTURER.contains("Genymotion")
77
+ || Build.BRAND.startsWith("generic")
78
+ || Build.DEVICE.startsWith("generic")
79
+ || Build.PRODUCT == "google_sdk")
80
+ }
81
+ }
@@ -0,0 +1,16 @@
1
+ package com.pulseboard
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
+ class PulseBoardDevicePackage : ReactPackage {
9
+ override fun createNativeModules(
10
+ reactContext: ReactApplicationContext
11
+ ): List<NativeModule> = listOf(PulseBoardDeviceModule(reactContext))
12
+
13
+ override fun createViewManagers(
14
+ reactContext: ReactApplicationContext
15
+ ): List<ViewManager<*, *>> = emptyList()
16
+ }
@@ -0,0 +1,81 @@
1
+ package com.pulseboard
2
+
3
+ import android.content.Context
4
+ import android.net.ConnectivityManager
5
+ import android.net.NetworkCapabilities
6
+ import android.os.Build
7
+ import android.telephony.TelephonyManager
8
+ import com.facebook.react.bridge.*
9
+ import java.net.NetworkInterface
10
+
11
+ class PulseBoardNetworkModule(private val reactContext: ReactApplicationContext) :
12
+ ReactContextBaseJavaModule(reactContext) {
13
+
14
+ override fun getName() = "PulseBoardNetwork"
15
+
16
+ @ReactMethod
17
+ fun getNetworkInfo(promise: Promise) {
18
+ try {
19
+ val connectivityManager = reactContext
20
+ .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
21
+
22
+ val result = Arguments.createMap().apply {
23
+ putString("type", getConnectionType(connectivityManager))
24
+ putBoolean("isConnected", isConnected(connectivityManager))
25
+ putBoolean("isWifiEnabled", isWifi(connectivityManager))
26
+ putString("carrier", getCarrier())
27
+ putString("ipAddress", getIPAddress() ?: "unknown")
28
+ }
29
+
30
+ promise.resolve(result)
31
+ } catch (e: Exception) {
32
+ promise.reject("NETWORK_INFO_ERROR", e.message, e)
33
+ }
34
+ }
35
+
36
+ private fun getConnectionType(cm: ConnectivityManager): String {
37
+ val network = cm.activeNetwork ?: return "offline"
38
+ val caps = cm.getNetworkCapabilities(network) ?: return "unknown"
39
+
40
+ return when {
41
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
42
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular"
43
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet"
44
+ else -> "unknown"
45
+ }
46
+ }
47
+
48
+ private fun isConnected(cm: ConnectivityManager): Boolean {
49
+ val network = cm.activeNetwork ?: return false
50
+ val caps = cm.getNetworkCapabilities(network) ?: return false
51
+ return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
52
+ }
53
+
54
+ private fun isWifi(cm: ConnectivityManager): Boolean {
55
+ val network = cm.activeNetwork ?: return false
56
+ val caps = cm.getNetworkCapabilities(network) ?: return false
57
+ return caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
58
+ }
59
+
60
+ private fun getCarrier(): String {
61
+ return try {
62
+ val telephonyManager = reactContext
63
+ .getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
64
+ telephonyManager.networkOperatorName.takeIf { it.isNotEmpty() } ?: "unknown"
65
+ } catch (e: Exception) {
66
+ "unknown"
67
+ }
68
+ }
69
+
70
+ private fun getIPAddress(): String? {
71
+ return try {
72
+ NetworkInterface.getNetworkInterfaces()
73
+ .asSequence()
74
+ .flatMap { it.inetAddresses.asSequence() }
75
+ .firstOrNull { !it.isLoopbackAddress && it.hostAddress?.contains(':') == false }
76
+ ?.hostAddress
77
+ } catch (e: Exception) {
78
+ null
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,16 @@
1
+ package com.pulseboard
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
+ class PulseBoardNetworkPackage : ReactPackage {
9
+ override fun createNativeModules(
10
+ reactContext: ReactApplicationContext
11
+ ): List<NativeModule> = listOf(PulseBoardNetworkModule(reactContext))
12
+
13
+ override fun createViewManagers(
14
+ reactContext: ReactApplicationContext
15
+ ): List<ViewManager<*, *>> = emptyList()
16
+ }
@@ -0,0 +1,19 @@
1
+ package com.pulseboard
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
+ class PulseBoardPackage : ReactPackage {
9
+ override fun createNativeModules(
10
+ reactContext: ReactApplicationContext
11
+ ): List<NativeModule> = listOf(
12
+ PulseBoardDeviceModule(reactContext),
13
+ PulseBoardNetworkModule(reactContext)
14
+ )
15
+
16
+ override fun createViewManagers(
17
+ reactContext: ReactApplicationContext
18
+ ): List<ViewManager<*, *>> = emptyList()
19
+ }
@@ -0,0 +1,103 @@
1
+ type EventType = "error" | "event" | "metric";
2
+ type Platform = "ios" | "android" | "unknown";
3
+ type NetworkType = "wifi" | "cellular" | "offline" | "unknown";
4
+ type AppContext = {
5
+ appVersion?: string;
6
+ buildNumber?: string;
7
+ environment?: "production" | "staging" | "development";
8
+ };
9
+ type UserContext = {
10
+ userId?: string;
11
+ email?: string;
12
+ username?: string;
13
+ };
14
+ type DeviceContext = {
15
+ platform: Platform;
16
+ os: string;
17
+ osVersion: string | number;
18
+ model: string;
19
+ manufacturer: string;
20
+ brand: string;
21
+ isTablet: boolean;
22
+ appVersion: string;
23
+ buildNumber: string;
24
+ bundleId: string;
25
+ screenWidth: number;
26
+ screenHeight: number;
27
+ fontScale: number;
28
+ isEmulator: boolean;
29
+ language: string;
30
+ timezone: string;
31
+ };
32
+ type NetworkContext = {
33
+ type: NetworkType;
34
+ isConnected: boolean;
35
+ isWifiEnabled: boolean;
36
+ carrier: string;
37
+ ipAddress: string;
38
+ };
39
+ type SessionContext = {
40
+ sessionId: string;
41
+ startedAt: string;
42
+ };
43
+ type EnrichedContext = {
44
+ app: AppContext;
45
+ device: DeviceContext;
46
+ network: NetworkContext;
47
+ session: SessionContext;
48
+ user: UserContext;
49
+ };
50
+ type PulseEvent = {
51
+ apiKey: string;
52
+ type: EventType;
53
+ name: string;
54
+ payload: Record<string, unknown>;
55
+ timestamp: string;
56
+ context?: EnrichedContext;
57
+ };
58
+ type SDKConfig = {
59
+ apiKey: string;
60
+ host: string;
61
+ app?: AppContext;
62
+ autoCapture?: boolean;
63
+ debug?: boolean;
64
+ flushInterval?: number;
65
+ maxQueueSize?: number;
66
+ };
67
+ type TrackOptions = {
68
+ payload?: Record<string, unknown>;
69
+ timestamp?: string;
70
+ };
71
+ type CaptureErrorOptions = {
72
+ payload?: Record<string, unknown>;
73
+ };
74
+
75
+ declare class PulseBoardSDK {
76
+ private config;
77
+ private client;
78
+ private queue;
79
+ private autoCapture;
80
+ private contextCollector;
81
+ private flushTimer;
82
+ private initialized;
83
+ init(config: SDKConfig): void;
84
+ getContext(): Promise<EnrichedContext>;
85
+ identify(user: UserContext): void;
86
+ clearUser(): void;
87
+ track(name: string, options?: TrackOptions): void;
88
+ metric(name: string, value: number, options?: TrackOptions): void;
89
+ captureError(error: Error, options?: CaptureErrorOptions): void;
90
+ startSession(): void;
91
+ endSession(duration?: number): void;
92
+ trackScreen(screenName: string, loadTime?: number): void;
93
+ trackApiCall(endpoint: string, httpMethod: string, statusCode: number, duration: number, payloadSize?: number): void;
94
+ trackCrash(error: Error, isFatal?: boolean): void;
95
+ flush(): Promise<void>;
96
+ destroy(): void;
97
+ private buildAndEnqueue;
98
+ private assertInitialized;
99
+ private log;
100
+ }
101
+ declare const PulseBoard: PulseBoardSDK;
102
+
103
+ export { type AppContext, type CaptureErrorOptions, type DeviceContext, type EnrichedContext, type EventType, type NetworkContext, PulseBoard, type PulseEvent, type SDKConfig, type SessionContext, type TrackOptions, type UserContext };
@@ -0,0 +1,103 @@
1
+ type EventType = "error" | "event" | "metric";
2
+ type Platform = "ios" | "android" | "unknown";
3
+ type NetworkType = "wifi" | "cellular" | "offline" | "unknown";
4
+ type AppContext = {
5
+ appVersion?: string;
6
+ buildNumber?: string;
7
+ environment?: "production" | "staging" | "development";
8
+ };
9
+ type UserContext = {
10
+ userId?: string;
11
+ email?: string;
12
+ username?: string;
13
+ };
14
+ type DeviceContext = {
15
+ platform: Platform;
16
+ os: string;
17
+ osVersion: string | number;
18
+ model: string;
19
+ manufacturer: string;
20
+ brand: string;
21
+ isTablet: boolean;
22
+ appVersion: string;
23
+ buildNumber: string;
24
+ bundleId: string;
25
+ screenWidth: number;
26
+ screenHeight: number;
27
+ fontScale: number;
28
+ isEmulator: boolean;
29
+ language: string;
30
+ timezone: string;
31
+ };
32
+ type NetworkContext = {
33
+ type: NetworkType;
34
+ isConnected: boolean;
35
+ isWifiEnabled: boolean;
36
+ carrier: string;
37
+ ipAddress: string;
38
+ };
39
+ type SessionContext = {
40
+ sessionId: string;
41
+ startedAt: string;
42
+ };
43
+ type EnrichedContext = {
44
+ app: AppContext;
45
+ device: DeviceContext;
46
+ network: NetworkContext;
47
+ session: SessionContext;
48
+ user: UserContext;
49
+ };
50
+ type PulseEvent = {
51
+ apiKey: string;
52
+ type: EventType;
53
+ name: string;
54
+ payload: Record<string, unknown>;
55
+ timestamp: string;
56
+ context?: EnrichedContext;
57
+ };
58
+ type SDKConfig = {
59
+ apiKey: string;
60
+ host: string;
61
+ app?: AppContext;
62
+ autoCapture?: boolean;
63
+ debug?: boolean;
64
+ flushInterval?: number;
65
+ maxQueueSize?: number;
66
+ };
67
+ type TrackOptions = {
68
+ payload?: Record<string, unknown>;
69
+ timestamp?: string;
70
+ };
71
+ type CaptureErrorOptions = {
72
+ payload?: Record<string, unknown>;
73
+ };
74
+
75
+ declare class PulseBoardSDK {
76
+ private config;
77
+ private client;
78
+ private queue;
79
+ private autoCapture;
80
+ private contextCollector;
81
+ private flushTimer;
82
+ private initialized;
83
+ init(config: SDKConfig): void;
84
+ getContext(): Promise<EnrichedContext>;
85
+ identify(user: UserContext): void;
86
+ clearUser(): void;
87
+ track(name: string, options?: TrackOptions): void;
88
+ metric(name: string, value: number, options?: TrackOptions): void;
89
+ captureError(error: Error, options?: CaptureErrorOptions): void;
90
+ startSession(): void;
91
+ endSession(duration?: number): void;
92
+ trackScreen(screenName: string, loadTime?: number): void;
93
+ trackApiCall(endpoint: string, httpMethod: string, statusCode: number, duration: number, payloadSize?: number): void;
94
+ trackCrash(error: Error, isFatal?: boolean): void;
95
+ flush(): Promise<void>;
96
+ destroy(): void;
97
+ private buildAndEnqueue;
98
+ private assertInitialized;
99
+ private log;
100
+ }
101
+ declare const PulseBoard: PulseBoardSDK;
102
+
103
+ export { type AppContext, type CaptureErrorOptions, type DeviceContext, type EnrichedContext, type EventType, type NetworkContext, PulseBoard, type PulseEvent, type SDKConfig, type SessionContext, type TrackOptions, type UserContext };