refacto/migrate-turborepo

This commit is contained in:
Simon Boisset 2023-08-21 23:43:50 +02:00
parent 9905366e9a
commit a9507e7d3d
134 changed files with 20842 additions and 475 deletions

View file

@ -0,0 +1,21 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
- run: npm install
- run: npm run build
- run: npm test

6
packages/react-native/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
node_modules
dist
coverage
project.xcworkspace
.DS_Store

View file

@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"editor.tabSize": 2
}

View file

@ -0,0 +1,17 @@
## 0.2.0
- Automatic flush of events on app exit
- Events are now sent in batches to reduce network overhead
- While offline, events will be enqueue and sent when the app is back online
## 0.1.2
- Added an option to set the appVersion during init
## 0.1.1
- Fixed some links on package.json
## 0.1.0
- Initial release

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Sumbit Labs Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,49 @@
![Aptabase](https://aptabase.com/og.png)
# React Native SDK for Aptabase
Instrument your React Native or Expo apps with Aptabase, an Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps.
## Install
Install the SDK using `npm` or your preferred JavaScript package manager
```bash
npm add @aptabase/react-native
```
## Usage
First, you need to get your `App Key` from Aptabase, you can find it in the `Instructions` menu on the left side menu.
Initialize the SDK as early as possible, ideally before declaring the `App` component:
```js
import { init } from "@aptabase/react-native";
init("<YOUR_APP_KEY>"); // 👈 this is where you enter your App Key
```
Afterwards, you can start tracking events with `trackEvent`:
```js
import { trackEvent } from "@aptabase/react-native";
trackEvent("app_started"); // An event with no properties
trackEvent("screen_view", { name: "Settings" }); // An event with a custom property
```
**Note for Expo apps:** Events sent during development while running on Expo Go will not have the `App Version` property because native modules are not available in Expo Go. However, when you build your app and run it on a real device, the `App Version` property will be available. Alternatively, you can also set the `appVersion` during the `init` call so that it's available during development as well.
A few important notes:
1. The SDK will automatically enhance the event with some useful information, like the OS, the app version, and other things.
2. You're in control of what gets sent to Aptabase. This SDK does not automatically track any events, you need to call `trackEvent` manually.
- Because of this, it's generally recommended to at least track an event at startup
3. You do not need to await for the `trackEvent` function, it'll run in the background.
4. Only strings and numbers values are allowed on custom properties
## Preparing for Submission to Apple App Store
When submitting your app to the Apple App Store, you'll need to fill out the `App Privacy` form. You can find all the answers on our [How to fill out the Apple App Privacy when using Aptabase](https://aptabase.com/docs/apple-app-privacy) guide.

View file

@ -0,0 +1,27 @@
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
apply plugin: 'com.android.library'
android {
compileSdkVersion safeExtGet('compileSdkVersion', 27)
buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27)
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
lintOptions {
warning 'InvalidPackage'
}
}
dependencies {
implementation 'com.facebook.react:react-native:+'
}

View file

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.aptabase.aptabase">
</manifest>

View file

@ -0,0 +1,44 @@
package com.aptabase.aptabase;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import java.util.HashMap;
import java.util.Map;
public class RNAptabaseModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
public RNAptabaseModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "RNAptabaseModule";
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
final PackageManager packageManager = this.reactContext.getPackageManager();
final String packageName = this.reactContext.getPackageName();
try {
constants.put("appVersion", packageManager.getPackageInfo(packageName, 0).versionName);
constants.put("appBuildNumber", packageManager.getPackageInfo(packageName, 0).versionCode);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return constants;
}
}

View file

@ -0,0 +1,27 @@
package com.aptabase.aptabase;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
public class RNAptabasePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNAptabaseModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View file

@ -0,0 +1,19 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
s.name = "aptabase-react-native"
s.version = package['version']
s.summary = package['description']
s.license = package['license']
s.authors = package['author']
s.homepage = package['homepage']
s.platform = :ios, "10.0"
s.source = { :git => "https://github.com/demchenkoalex/react-native-module-template.git", :tag => "v#{s.version}" }
s.source_files = "ios/**/*.{h,m,swift}"
s.dependency 'React'
end

View file

@ -0,0 +1,35 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo

View file

@ -0,0 +1,29 @@
import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";
import { init, trackEvent } from "@aptabase/react-native";
init("A-DEV-0000000000");
trackEvent("app_started");
export default function App() {
const onClick = () => {
trackEvent("Hello");
};
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Button onPress={onClick} title="Click Me" color="#841584" />
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});

View file

@ -0,0 +1,30 @@
{
"expo": {
"name": "HelloWorldExpo",
"slug": "HelloWorldExpo",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,6 @@
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
{
"name": "helloworldexpo",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@aptabase/react-native": "file:../..",
"expo": "~49.0.7",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-native": "0.72.3"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}

View file

@ -0,0 +1 @@
#import <React/RCTBridgeModule.h>

View file

@ -0,0 +1,4 @@
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(RNAptabaseModule, NSObject)
@end

View file

@ -0,0 +1,17 @@
import Foundation
@objc(RNAptabaseModule)
class RNAptabaseModule: NSObject {
@objc
func constantsToExport() -> [AnyHashable : Any]! {
return [
"appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as Any,
"appBuildNumber": Bundle.main.infoDictionary?["CFBundleVersion"] as Any
]
}
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
}

View file

@ -0,0 +1,314 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
FA4F9FE82512AA42002DB4D5 /* RNAptabaseModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4F9FE72512AA42002DB4D5 /* RNAptabaseModule.swift */; };
FA4F9FEB2512ACC2002DB4D5 /* RNAptabaseModule.m in Sources */ = {isa = PBXBuildFile; fileRef = FA4F9FEA2512ACC2002DB4D5 /* RNAptabaseModule.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
FA0EFF5E236CC8FB00069FA8 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
FA0EFF60236CC8FB00069FA8 /* libRNAptabaseModule.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNAptabaseModule.a; sourceTree = BUILT_PRODUCTS_DIR; };
FA4F9FE62512AA41002DB4D5 /* RNAptabaseModule-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNAptabaseModule-Bridging-Header.h"; sourceTree = "<group>"; };
FA4F9FE72512AA42002DB4D5 /* RNAptabaseModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNAptabaseModule.swift; sourceTree = "<group>"; };
FA4F9FEA2512ACC2002DB4D5 /* RNAptabaseModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNAptabaseModule.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
FA0EFF5D236CC8FB00069FA8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
FA0EFF57236CC8FB00069FA8 = {
isa = PBXGroup;
children = (
FA4F9FE62512AA41002DB4D5 /* RNAptabaseModule-Bridging-Header.h */,
FA4F9FEA2512ACC2002DB4D5 /* RNAptabaseModule.m */,
FA4F9FE72512AA42002DB4D5 /* RNAptabaseModule.swift */,
FA0EFF61236CC8FB00069FA8 /* Products */,
);
sourceTree = "<group>";
};
FA0EFF61236CC8FB00069FA8 /* Products */ = {
isa = PBXGroup;
children = (
FA0EFF60236CC8FB00069FA8 /* libRNAptabaseModule.a */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
FA0EFF5F236CC8FB00069FA8 /* RNAptabaseModule */ = {
isa = PBXNativeTarget;
buildConfigurationList = FA0EFF69236CC8FB00069FA8 /* Build configuration list for PBXNativeTarget "RNAptabaseModule" */;
buildPhases = (
FA0EFF5C236CC8FB00069FA8 /* Sources */,
FA0EFF5D236CC8FB00069FA8 /* Frameworks */,
FA0EFF5E236CC8FB00069FA8 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNAptabaseModule;
productName = RNAptabaseModule;
productReference = FA0EFF60236CC8FB00069FA8 /* libRNAptabaseModule.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
FA0EFF58236CC8FB00069FA8 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1240;
ORGANIZATIONNAME = "goenning";
TargetAttributes = {
FA0EFF5F236CC8FB00069FA8 = {
CreatedOnToolsVersion = 11.1;
LastSwiftMigration = 1170;
};
};
};
buildConfigurationList = FA0EFF5B236CC8FB00069FA8 /* Build configuration list for PBXProject "RNAptabaseModule" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = FA0EFF57236CC8FB00069FA8;
productRefGroup = FA0EFF61236CC8FB00069FA8 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
FA0EFF5F236CC8FB00069FA8 /* RNAptabaseModule */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
FA0EFF5C236CC8FB00069FA8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FA4F9FE82512AA42002DB4D5 /* RNAptabaseModule.swift in Sources */,
FA4F9FEB2512ACC2002DB4D5 /* RNAptabaseModule.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
FA0EFF67236CC8FB00069FA8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
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_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
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 = 11.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
FA0EFF68236CC8FB00069FA8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
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_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
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 = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
FA0EFF6A236CC8FB00069FA8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../example/ios/Pods/Headers/Public/React-Core",
"$(SRCROOT)/../../../ios/Pods/Headers/Public/React-Core",
);
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "RNAptabaseModule-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
FA0EFF6B236CC8FB00069FA8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../example/ios/Pods/Headers/Public/React-Core",
"$(SRCROOT)/../../../ios/Pods/Headers/Public/React-Core",
);
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "RNAptabaseModule-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
FA0EFF5B236CC8FB00069FA8 /* Build configuration list for PBXProject "RNAptabaseModule" */ = {
isa = XCConfigurationList;
buildConfigurations = (
FA0EFF67236CC8FB00069FA8 /* Debug */,
FA0EFF68236CC8FB00069FA8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
FA0EFF69236CC8FB00069FA8 /* Build configuration list for PBXNativeTarget "RNAptabaseModule" */ = {
isa = XCConfigurationList;
buildConfigurations = (
FA0EFF6A236CC8FB00069FA8 /* Debug */,
FA0EFF6B236CC8FB00069FA8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = FA0EFF58236CC8FB00069FA8 /* Project object */;
}

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FA0EFF5F236CC8FB00069FA8"
BuildableName = "libRNAptabaseModule.a"
BlueprintName = "RNAptabaseModule"
ReferencedContainer = "container:RNAptabaseModule.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FA0EFF5F236CC8FB00069FA8"
BuildableName = "libRNAptabaseModule.a"
BlueprintName = "RNAptabaseModule"
ReferencedContainer = "container:RNAptabaseModule.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,52 @@
{
"name": "@aptabase/react-native",
"version": "0.2.0",
"private": false,
"description": "React Native SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps",
"sideEffects": false,
"author": "goenning <goenning@aptabase.com>",
"main": "./dist/index.cjs.js",
"module": "./dist/index.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.cjs.js",
"import": "./dist/index.es.js",
"types": "./dist/index.d.ts"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/aptabase/aptabase-react-native.git"
},
"bugs": {
"url": "https://github.com/aptabase/aptabase-react-native/issues"
},
"homepage": "https://github.com/aptabase/aptabase-react-native",
"license": "MIT",
"scripts": {
"build": "vite build",
"test": "vitest run --coverage"
},
"files": [
"README.md",
"LICENSE",
"dist",
"package.json",
"android",
"ios",
"aptabase-react-native.podspec"
],
"devDependencies": {
"@rollup/plugin-replace": "5.0.2",
"@vitest/coverage-v8": "0.34.2",
"vite": "4.4.9",
"vite-plugin-dts": "3.5.2",
"vitest": "0.34.2",
"vitest-fetch-mock": "0.2.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
}

View file

@ -0,0 +1,8 @@
import createFetchMock from "vitest-fetch-mock";
import { vi } from "vitest";
vi.stubGlobal("__DEV__", true);
const fetchMocker = createFetchMock(vi);
fetchMocker.enableMocks();

View file

@ -0,0 +1,122 @@
import "vitest-fetch-mock";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AptabaseClient } from "./client";
import type { EnvironmentInfo } from "./env";
const env: EnvironmentInfo = {
isDebug: false,
locale: "en-US",
osName: "iOS",
osVersion: "14.3",
appVersion: "1.0.0",
appBuildNumber: "1",
sdkVersion: "aptabase-reactnative@1.0.0",
};
describe("AptabaseClient", () => {
beforeEach(() => {
vi.useFakeTimers();
fetchMock.resetMocks();
});
afterEach(() => {
vi.useRealTimers();
});
it("should allow override of appVersion", async () => {
const client = new AptabaseClient("A-DEV-000", env, {
appVersion: "2.0.0",
});
client.trackEvent("Hello");
await client.flush();
const body = await fetchMock.requests().at(0)?.json();
expect(body[0].eventName).toEqual("Hello");
expect(body[0].systemProps).toEqual({ ...env, appVersion: "2.0.0" });
});
it("should send event with correct props", async () => {
const client = new AptabaseClient("A-DEV-000", env);
client.trackEvent("test", { count: 1, foo: "bar" });
await client.flush();
const body = await fetchMock.requests().at(0)?.json();
expect(body[0].eventName).toEqual("test");
expect(body[0].props).toEqual({ count: 1, foo: "bar" });
expect(body[0].systemProps).toEqual(env);
});
it("should flush events every 500ms", async () => {
const client = new AptabaseClient("A-DEV-000", env);
client.startPolling(500);
client.trackEvent("Hello1");
vi.advanceTimersByTime(510);
expect(fetchMock.requests().length).toEqual(1);
const request1 = await fetchMock.requests().at(0)?.json();
expect(request1[0].eventName).toEqual("Hello1");
// after another tick, nothing should be sent
vi.advanceTimersByTime(510);
expect(fetchMock.requests().length).toEqual(1);
// after a trackEvent and another tick, the event should be sent
client.trackEvent("Hello2");
vi.advanceTimersByTime(510);
expect(fetchMock.requests().length).toEqual(2);
const request2 = await fetchMock.requests().at(1)?.json();
expect(request2[0].eventName).toEqual("Hello2");
});
it("should stop flush if polling stopped", async () => {
const client = new AptabaseClient("A-DEV-000", env);
client.startPolling(500);
client.trackEvent("Hello1");
vi.advanceTimersByTime(510);
expect(fetchMock.requests().length).toEqual(1);
// if polling stopped, no more events should be sent
client.stopPolling();
client.trackEvent("Hello2");
vi.advanceTimersByTime(5000);
expect(fetchMock.requests().length).toEqual(1);
});
it("should generate new session after long period of inactivity", async () => {
const client = new AptabaseClient("A-DEV-000", env);
client.trackEvent("Hello1");
await client.flush();
const request1 = await fetchMock.requests().at(0)?.json();
const sessionId1 = request1[0].sessionId;
expect(sessionId1).toBeDefined();
// after 10 minutes, the same session should be used
vi.advanceTimersByTime(10 * 60 * 1000);
client.trackEvent("Hello2");
await client.flush();
const request2 = await fetchMock.requests().at(1)?.json();
const sessionId2 = request2[0].sessionId;
expect(sessionId2).toBeDefined();
expect(sessionId2).toBe(sessionId1);
// after 2 hours, the same session should be used
vi.advanceTimersByTime(2 * 60 * 60 * 1000);
client.trackEvent("Hello3");
await client.flush();
const request3 = await fetchMock.requests().at(2)?.json();
const sessionId3 = request3[0].sessionId;
expect(sessionId3).toBeDefined();
expect(sessionId3).not.toBe(sessionId1);
});
});

View file

@ -0,0 +1,83 @@
import type { Platform } from "react-native";
import type { AptabaseOptions } from "./types";
import type { EnvironmentInfo } from "./env";
import { EventDispatcher } from "./dispatcher";
import { newSessionId } from "./session";
import { HOSTS, SESSION_TIMEOUT } from "./constants";
export class AptabaseClient {
private readonly _dispatcher: EventDispatcher;
private readonly _env: EnvironmentInfo;
private _sessionId = newSessionId();
private _lastTouched = new Date();
private _flushTimer: number | undefined;
constructor(appKey: string, env: EnvironmentInfo, options?: AptabaseOptions) {
const [_, region] = appKey.split("-");
const baseUrl = this.getBaseUrl(region, options);
this._env = { ...env };
if (options?.appVersion) {
this._env.appVersion = options.appVersion;
}
this._dispatcher = new EventDispatcher(appKey, baseUrl, env);
}
public trackEvent(
eventName: string,
props?: Record<string, string | number | boolean>
) {
this._dispatcher.enqueue({
timestamp: new Date().toISOString(),
sessionId: this.evalSessionId(),
eventName: eventName,
systemProps: {
isDebug: this._env.isDebug,
locale: this._env.locale,
osName: this._env.osName,
osVersion: this._env.osVersion,
appVersion: this._env.appVersion,
appBuildNumber: this._env.appBuildNumber,
sdkVersion: this._env.sdkVersion,
},
props: props,
});
}
public startPolling(flushInterval: number) {
this.stopPolling();
this._flushTimer = setInterval(this.flush.bind(this), flushInterval);
}
public stopPolling() {
if (this._flushTimer) {
clearInterval(this._flushTimer);
this._flushTimer = undefined;
}
}
public flush(): Promise<void> {
return this._dispatcher.flush();
}
private evalSessionId() {
let now = new Date();
const diffInMs = now.getTime() - this._lastTouched.getTime();
if (diffInMs > SESSION_TIMEOUT) {
this._sessionId = newSessionId();
}
this._lastTouched = now;
return this._sessionId;
}
private getBaseUrl(region: string, options?: AptabaseOptions): string {
if (region === "SH") {
return options?.host ?? HOSTS.DEV;
}
return HOSTS[region];
}
}

View file

@ -0,0 +1,14 @@
// Session expires after 1 hour of inactivity
export const SESSION_TIMEOUT = 60 * 60 * 1000;
// Flush events every 60 seconds in production, or 2 seconds in development
export const FLUSH_INTERVAL = __DEV__ ? 2000 : 60000;
// List of hosts for each region
// To use a self-hosted (SH) deployment, the host must be set during init
export const HOSTS: { [region: string]: string } = {
US: "https://us.aptabase.com",
EU: "https://eu.aptabase.com",
DEV: "http://localhost:3000",
SH: "",
};

View file

@ -0,0 +1,140 @@
import "vitest-fetch-mock";
import { EventDispatcher } from "./dispatcher";
import { beforeEach, describe, expect, it } from "vitest";
import { EnvironmentInfo } from "./env";
const env: EnvironmentInfo = {
isDebug: false,
locale: "en-US",
osName: "iOS",
osVersion: "14.3",
appVersion: "1.0.0",
appBuildNumber: "1",
sdkVersion: "aptabase-reactnative@1.0.0",
};
const createEvent = (eventName: string) => ({
timestamp: new Date().toISOString(),
sessionId: "123",
eventName,
systemProps: { ...env },
});
const expectRequestCount = (count: number) => {
expect(fetchMock.requests().length).toEqual(count);
};
const expectEventsCount = async (
requestIndex: number,
expectedNumOfEvents: number
) => {
const body = await fetchMock.requests().at(requestIndex)?.json();
expect(body.length).toEqual(expectedNumOfEvents);
};
describe("EventDispatcher", () => {
let dispatcher: EventDispatcher;
beforeEach(() => {
dispatcher = new EventDispatcher(
"A-DEV-000",
"https://localhost:3000",
env
);
fetchMock.resetMocks();
});
it("should not send a request if queue is empty", async () => {
await dispatcher.flush();
expectRequestCount(0);
});
it("should send even with correct headers", async () => {
dispatcher.enqueue(createEvent("app_started"));
await dispatcher.flush();
const request = await fetchMock.requests().at(0);
expect(request).not.toBeUndefined();
expect(request?.url).toEqual("https://localhost:3000/api/v0/events");
expect(request?.headers.get("Content-Type")).toEqual("application/json");
expect(request?.headers.get("App-Key")).toEqual("A-DEV-000");
expect(request?.headers.get("User-Agent")).toEqual("iOS/14.3 en-US");
});
it("should dispatch single event", async () => {
fetchMock.mockResponseOnce("{}");
dispatcher.enqueue(createEvent("app_started"));
await dispatcher.flush();
expectRequestCount(1);
await expectEventsCount(0, 1);
});
it("should not dispatch event if it's already been sent", async () => {
fetchMock.mockResponseOnce("{}");
dispatcher.enqueue(createEvent("app_started"));
await dispatcher.flush();
expectRequestCount(1);
await dispatcher.flush();
expectRequestCount(1);
});
it("should dispatch multiple events", async () => {
fetchMock.mockResponseOnce("{}");
dispatcher.enqueue(createEvent("app_started"));
dispatcher.enqueue(createEvent("app_exited"));
await dispatcher.flush();
expectRequestCount(1);
await expectEventsCount(0, 2);
});
it("should send many events in chunks of 25 items", async () => {
fetchMock.mockResponseOnce("{}");
for (let i = 0; i < 60; i++) {
dispatcher.enqueue(createEvent("hello_world"));
}
await dispatcher.flush();
expectRequestCount(3);
await expectEventsCount(0, 25);
await expectEventsCount(1, 25);
await expectEventsCount(2, 10);
});
it("should retry failed requests in a subsequent flush", async () => {
fetchMock.mockResponseOnce("{}", { status: 500 });
dispatcher.enqueue(createEvent("hello_world"));
await dispatcher.flush();
expectRequestCount(1);
await expectEventsCount(0, 1);
fetchMock.mockResponseOnce("{}", { status: 200 });
await dispatcher.flush();
expectRequestCount(2);
await expectEventsCount(1, 1);
});
it("should not retry requests that failed with 4xx", async () => {
fetchMock.mockResponseOnce("{}", { status: 400 });
dispatcher.enqueue(createEvent("hello_world"));
await dispatcher.flush();
expectRequestCount(1);
await expectEventsCount(0, 1);
await dispatcher.flush();
expectRequestCount(1);
});
});

View file

@ -0,0 +1,77 @@
import type { Event } from "./types";
import { EnvironmentInfo } from "./env";
export class EventDispatcher {
private _events: Event[] = [];
private MAX_BATCH_SIZE = 25;
private headers: Headers;
private apiUrl: string;
constructor(appKey: string, baseUrl: string, env: EnvironmentInfo) {
this.apiUrl = `${baseUrl}/api/v0/events`;
this.headers = new Headers({
"Content-Type": "application/json",
"App-Key": appKey,
"User-Agent": `${env.osName}/${env.osVersion} ${env.locale}`,
});
}
public enqueue(evt: Event | Event[]) {
if (Array.isArray(evt)) {
this._events.push(...evt);
return;
}
this._events.push(evt);
}
public async flush(): Promise<void> {
if (this._events.length === 0) {
return Promise.resolve();
}
let failedEvents: Event[] = [];
do {
const eventsToSend = this._events.splice(0, this.MAX_BATCH_SIZE);
try {
await this._sendEvents(eventsToSend);
} catch {
failedEvents = [...failedEvents, ...eventsToSend];
}
} while (this._events.length > 0);
if (failedEvents.length > 0) {
this.enqueue(failedEvents);
}
}
private async _sendEvents(events: Event[]): Promise<void> {
try {
const res = await fetch(this.apiUrl, {
method: "POST",
headers: this.headers,
credentials: "omit",
body: JSON.stringify(events),
});
if (res.status < 300) {
return Promise.resolve();
}
const reason = `${res.status} ${await res.text()}`;
if (res.status < 500) {
console.warn(
`Aptabase: Failed to send ${events.length} events because of ${reason}. Will not retry.`
);
return Promise.resolve();
}
throw new Error(reason);
} catch (e) {
console.error(
`Aptabase: Failed to send ${events.length} events. Reason: ${e}`
);
throw e;
}
}
}

View file

@ -0,0 +1,45 @@
import { Platform } from "react-native";
import version from "./version";
// env.PKG_VERSION is replaced by Vite during build phase
const sdkVersion = "aptabase-reactnative@env.PKG_VERSION";
export interface EnvironmentInfo {
isDebug: boolean;
locale: string;
appVersion: string;
appBuildNumber: string;
sdkVersion: string;
osName: string;
osVersion: string;
}
export function getEnvironmentInfo(): EnvironmentInfo {
const [osName, osVersion] = getOperatingSystem();
const locale = "en-US";
return {
appVersion: version.appVersion || "",
appBuildNumber: version.appBuildNumber || "",
isDebug: __DEV__,
locale,
osName,
osVersion,
sdkVersion,
};
}
function getOperatingSystem(): [string, string] {
switch (Platform.OS) {
case "android":
return ["Android", Platform.Version.toString()];
case "ios":
if (Platform.isPad) {
return ["iPadOS", Platform.Version];
}
return ["iOS", Platform.Version];
default:
return ["", ""];
}
}

View file

@ -0,0 +1,55 @@
import type { AptabaseOptions } from "./types";
import { getEnvironmentInfo } from "./env";
import { AppState, Platform } from "react-native";
import { AptabaseClient } from "./client";
import { FLUSH_INTERVAL } from "./constants";
import { validate } from "./validate";
let _client: AptabaseClient | undefined;
/**
* Initializes the SDK with given App Key
* @param {string} appKey - Aptabase App Key
* @param {AptabaseOptions} options - Optional initialization parameters
*/
export function init(appKey: string, options?: AptabaseOptions) {
const [ok, msg] = validate(Platform.OS, appKey, options);
if (!ok) {
console.warn(`Aptabase: ${msg}. Tracking will be disabled.`);
return;
}
const env = getEnvironmentInfo();
_client = new AptabaseClient(appKey, env, options);
const flushInterval = options?.flushInterval ?? FLUSH_INTERVAL;
_client.startPolling(flushInterval);
if (!AppState.isAvailable) return;
AppState.addEventListener("change", (next) => {
_client?.stopPolling();
switch (next) {
case "active":
_client?.startPolling(flushInterval);
break;
case "background":
_client?.flush();
break;
}
});
}
/**
* Track an event using given properties
* @param {string} eventName - The name of the event to track
* @param {Object} props - Optional custom properties
*/
export function trackEvent(
eventName: string,
props?: Record<string, string | number | boolean>
) {
_client?.trackEvent(eventName, props);
}

View file

@ -0,0 +1,13 @@
import { describe, expect, it } from "vitest";
import { newSessionId } from "./session";
describe("Session", () => {
it("should generate session ids", async () => {
const id = newSessionId();
expect(id).toHaveLength(36);
const uuidRegex =
/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/;
expect(id).toMatch(uuidRegex);
});
});

View file

@ -0,0 +1,19 @@
export function newSessionId() {
return [
randomStr(8),
randomStr(4),
randomStr(4),
randomStr(4),
randomStr(12),
].join("-");
}
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
function randomStr(len: number) {
let result = "";
for (let i = 0; i < len; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}

33
packages/react-native/src/types.d.ts vendored Normal file
View file

@ -0,0 +1,33 @@
/**
* Custom initialization parameters for Aptabase SDK.
* Use this when calling the init function.
*/
export type AptabaseOptions = {
// Host URL for Self-Hosted deployments
host?: string;
// Custom appVersion to override the default
appVersion?: string;
// Override the default flush interval (in milliseconds)
flushInterval?: number;
};
/**
* A tracked event instance representing something that happened in the app.
*/
export type Event = {
timestamp: string;
sessionId: string;
eventName: string;
systemProps: {
isDebug: boolean;
locale: string;
osName: string;
osVersion: string;
appVersion: string;
appBuildNumber: string;
sdkVersion: string;
};
props?: Record<string, string | number | boolean>;
};

View file

@ -0,0 +1,47 @@
import { describe, expect, it } from "vitest";
import { validate } from "./validate";
describe("Validate", () => {
[
{
platform: "ios" as const,
appKey: "A-DEV-000",
options: undefined,
expected: [true, ""],
},
{
platform: "ios" as const,
appKey: "A-SH-1234",
options: {
host: "https://aptabase.mycompany.com",
},
expected: [true, ""],
},
{
platform: "web" as const,
appKey: "A-DEV-000",
options: undefined,
expected: [false, "This SDK is only supported on Android and iOS."],
},
{
platform: "ios" as const,
appKey: "A-WTF-000",
options: undefined,
expected: [false, 'App Key "A-WTF-000" is invalid'],
},
{
platform: "ios" as const,
appKey: "A-SH-1234",
options: undefined,
expected: [
false,
"Host parameter must be defined when using Self-Hosted App Key.",
],
},
].forEach(({ platform, appKey, options, expected }) => {
it(`should validate ${platform} ${appKey} ${options}`, async () => {
const result = validate(platform, appKey, options);
expect(result).toEqual(expected);
});
});
});

View file

@ -0,0 +1,28 @@
import type { Platform } from "react-native";
import { HOSTS } from "./constants";
import type { AptabaseOptions } from "./types";
export function validate(
platform: typeof Platform.OS,
appKey: string,
options?: AptabaseOptions
): [boolean, string] {
if (platform !== "android" && platform !== "ios") {
return [false, "This SDK is only supported on Android and iOS."];
}
const parts = appKey.split("-");
if (parts.length !== 3 || HOSTS[parts[1]] === undefined) {
return [false, `App Key "${appKey}" is invalid`];
}
if (parts[1] === "SH" && !options?.host) {
return [
false,
`Host parameter must be defined when using Self-Hosted App Key.`,
];
}
return [true, ""];
}

View file

@ -0,0 +1,16 @@
import { NativeModules } from 'react-native';
const { RNAptabaseModule } = NativeModules;
type VersionObject = {
appVersion: string | undefined,
appBuildNumber: string | undefined,
};
const Version: VersionObject = {
appVersion: RNAptabaseModule && RNAptabaseModule.appVersion,
appBuildNumber: RNAptabaseModule && RNAptabaseModule.appBuildNumber,
};
export default Version;

View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"outDir": "dist",
"strict": true,
"jsx": "preserve",
"allowJs": true,
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"types": ["react-native"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"lib": ["esnext", "dom"]
},
"include": ["src/index.ts", "*.d.ts"]
}

View file

@ -0,0 +1,33 @@
import replace from "@rollup/plugin-replace";
import path from "path";
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
import pkg from "./package.json" assert { type: "json" };
export default defineConfig({
build: {
lib: {
formats: ["cjs", "es"],
entry: {
index: path.resolve(__dirname, "src/index.ts"),
},
name: "@aptabase/react-native",
fileName: (format, entryName) => `${entryName}.${format}.js`,
},
rollupOptions: {
external: ["react", "react-native"],
},
},
test: {
setupFiles: ["./setupVitest.ts"],
coverage: {
reporter: ["lcov", "text"],
},
},
plugins: [
dts(),
replace({
"env.PKG_VERSION": pkg.version,
}),
],
});