@minqian/react-native-umeng 0.0.0-stage → 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.
- package/README.md +80 -2
- package/android/build.gradle +37 -0
- package/android/proguard-rules.pro +8 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/minqian/umeng/UmengModule.kt +123 -0
- package/app.plugin.js +1 -0
- package/build/UmengNativeModule.d.ts +16 -0
- package/build/UmengNativeModule.d.ts.map +1 -0
- package/build/UmengNativeModule.js +23 -0
- package/build/UmengNativeModule.js.map +1 -0
- package/build/index.d.ts +72 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +93 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/ReactNativeUmeng.podspec +29 -0
- package/ios/UmengBridge.h +28 -0
- package/ios/UmengBridge.m +49 -0
- package/ios/UmengModule.swift +63 -0
- package/package.json +52 -4
- package/plugin/build/withUmeng.d.ts +11 -0
- package/plugin/build/withUmeng.js +103 -0
- package/src/UmengNativeModule.ts +38 -0
- package/src/index.ts +136 -0
package/README.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @minqian/react-native-umeng
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向 Expo / React Native 的友盟统计(统计上报)轻量封装。
|
|
4
|
+
|
|
5
|
+
- **同时支持 Android 与 iOS**,两端 API 完全一致,少量行为差异见下文。
|
|
6
|
+
- **Android 延迟 / 两段式初始化**,符合国内隐私合规要求;iOS 端 `preInit` 为 no-op。
|
|
7
|
+
- **自动会话跟踪**,基于 Expo 的 Activity 生命周期钩子实现(Android)。
|
|
8
|
+
- **内置 Expo Config Plugin**:Android 自动注入 `UMENG_APPKEY`、`UMENG_CHANNEL` 元数据及阿里云 Maven 镜像;iOS 自动为模拟器排除 `arm64` 架构以兼容老版本 Umeng framework。
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
npm i file:../react-native-umeng # 在你的 Expo 项目中执行
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`app.json` 配置:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"expo": {
|
|
21
|
+
"plugins": [
|
|
22
|
+
["@minqian/react-native-umeng", { "appKey": "<你的友盟 App Key>", "channel": "Umeng" }]
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
然后执行 `npx expo prebuild --clean`,再按平台运行:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
npx expo run:android
|
|
32
|
+
npx expo run:ios
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 使用
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import Umeng from '@minqian/react-native-umeng';
|
|
39
|
+
|
|
40
|
+
// App 启动后即可调用(在用户同意隐私政策前也安全)。
|
|
41
|
+
Umeng.preInit('<appKey>', 'Umeng');
|
|
42
|
+
|
|
43
|
+
// 用户同意隐私政策之后再调用:
|
|
44
|
+
Umeng.init('<appKey>', 'Umeng', { logEnabled: __DEV__ });
|
|
45
|
+
|
|
46
|
+
// 上报自定义事件。
|
|
47
|
+
Umeng.onEvent('home_open', { source: 'tab' });
|
|
48
|
+
|
|
49
|
+
// 用户画像(可选)。
|
|
50
|
+
Umeng.onProfileSignIn('user-123');
|
|
51
|
+
Umeng.onProfileSignOff();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
| 方法 | 说明 |
|
|
57
|
+
|------|------|
|
|
58
|
+
| `setLogEnabled(enabled)` | 开关友盟 SDK 详细日志 |
|
|
59
|
+
| `preInit(appKey, channel?)` | 预初始化,不上报数据 |
|
|
60
|
+
| `init(appKey, channel?, options?)` | 正式初始化 |
|
|
61
|
+
| `isInitialized()` | 同步获取初始化状态 |
|
|
62
|
+
| `onEvent(eventId, properties?)` | 自定义事件上报 |
|
|
63
|
+
| `onProfileSignIn(userId, provider?)` | 标记当前会话的用户 |
|
|
64
|
+
| `onProfileSignOff()` | 清除用户标记 |
|
|
65
|
+
| `onPageStart(name)` / `onPageEnd(name)` | 手动页面统计 |
|
|
66
|
+
| `reportError(message)` | 自定义错误上报 |
|
|
67
|
+
|
|
68
|
+
会话生命周期(`onResume` / `onPause`)在 Android 上由 Expo Module 内部的 `OnActivityEntersForeground/Background` 钩子自动驱动,因此**无需**像旧版 SDK 文档建议的那样去重写 `MainActivity`。iOS 端由友盟 SDK 自身处理。
|
|
69
|
+
|
|
70
|
+
## 平台差异
|
|
71
|
+
|
|
72
|
+
| 行为 | Android | iOS |
|
|
73
|
+
|------|---------|-----|
|
|
74
|
+
| `preInit` | 真正的预初始化,仅做合规准备,不上报 | no-op(友盟 iOS SDK 没有两段式初始化) |
|
|
75
|
+
| `init` | 调用 `UMConfigure.init` | 调用 `UMConfigure setAppKey:channel:` |
|
|
76
|
+
| `reportError` | 调用 `MobclickAgent.reportError` | UMCAnalytics 6.0 无官方错误 API,退化为自定义事件 `rn_reported_error` |
|
|
77
|
+
| 模拟器架构 | 不涉及 | Config Plugin 自动在 pbxproj 中为 Debug 配置排除 `arm64`,以兼容老版 Umeng framework |
|
|
78
|
+
|
|
79
|
+
## 许可证
|
|
80
|
+
|
|
81
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id 'com.android.library'
|
|
3
|
+
id 'expo-module-gradle-plugin'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
group = 'com.minqian.umeng'
|
|
7
|
+
version = '0.1.0'
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
namespace "com.minqian.umeng"
|
|
11
|
+
defaultConfig {
|
|
12
|
+
versionCode 1
|
|
13
|
+
versionName "0.1.0"
|
|
14
|
+
consumerProguardFiles 'proguard-rules.pro'
|
|
15
|
+
}
|
|
16
|
+
lintOptions {
|
|
17
|
+
abortOnError false
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
repositories {
|
|
22
|
+
maven { url 'https://maven.aliyun.com/repository/public' }
|
|
23
|
+
maven { url 'https://repo1.maven.org/maven2/' }
|
|
24
|
+
mavenCentral()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
dependencies {
|
|
28
|
+
// Umeng common SDK + the asms native bridge.
|
|
29
|
+
// - common 9.4.0: long-standing stable release, widely used in production.
|
|
30
|
+
// - asms is bumped to 1.6.3 (paired with the 9.4.x common line, modern JNI
|
|
31
|
+
// surface). NOTE: no asms release (1.2.3 / 1.6.3 / 1.8.7.2 all checked)
|
|
32
|
+
// ships an x86_64 libumeng-spy.so — Umeng's matrix is armeabi-v7a +
|
|
33
|
+
// arm64-v8a + x86 only. Real-device testing should use an arm64 phone;
|
|
34
|
+
// an x86_64 emulator will fail to load the native lib at init.
|
|
35
|
+
implementation 'com.umeng.umsdk:common:9.4.0'
|
|
36
|
+
implementation 'com.umeng.umsdk:asms:1.6.3'
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Umeng analytics — keep public SDK surface intact in release builds.
|
|
2
|
+
-keep class com.umeng.** { *; }
|
|
3
|
+
-keep class com.uc.** { *; }
|
|
4
|
+
-keep class com.efs.** { *; }
|
|
5
|
+
-keep class org.repackage.** { *; }
|
|
6
|
+
-dontwarn com.umeng.**
|
|
7
|
+
-dontwarn com.uc.**
|
|
8
|
+
-dontwarn com.efs.**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest />
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
package com.minqian.umeng
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.umeng.analytics.MobclickAgent
|
|
5
|
+
import com.umeng.commonsdk.UMConfigure
|
|
6
|
+
import expo.modules.kotlin.modules.Module
|
|
7
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Umeng (友盟+) Analytics module for Expo.
|
|
11
|
+
*
|
|
12
|
+
* Implementation notes:
|
|
13
|
+
* - `preInit` is safe to call *before* the user accepts the privacy policy.
|
|
14
|
+
* - `init` actually starts collecting + uploading. Call it once consent is in.
|
|
15
|
+
* - Session events (`onResume` / `onPause`) are wired automatically through
|
|
16
|
+
* Expo's `OnActivityEntersForeground/Background` hooks, so the host app
|
|
17
|
+
* does not need to override MainActivity.
|
|
18
|
+
*/
|
|
19
|
+
class UmengModule : Module() {
|
|
20
|
+
|
|
21
|
+
companion object {
|
|
22
|
+
private const val TAG = "UmengModule"
|
|
23
|
+
|
|
24
|
+
@Volatile
|
|
25
|
+
private var initialized: Boolean = false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun definition() = ModuleDefinition {
|
|
29
|
+
Name("Umeng")
|
|
30
|
+
|
|
31
|
+
// ---- Lifecycle: drive Umeng session start/end ----
|
|
32
|
+
OnActivityEntersForeground {
|
|
33
|
+
val ctx = appContext.reactContext ?: return@OnActivityEntersForeground
|
|
34
|
+
if (initialized) {
|
|
35
|
+
MobclickAgent.onResume(ctx)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
OnActivityEntersBackground {
|
|
40
|
+
val ctx = appContext.reactContext ?: return@OnActivityEntersBackground
|
|
41
|
+
if (initialized) {
|
|
42
|
+
MobclickAgent.onPause(ctx)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---- Configuration / init ----
|
|
47
|
+
Function("setLogEnabled") { enabled: Boolean ->
|
|
48
|
+
UMConfigure.setLogEnabled(enabled)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Function("preInit") { appKey: String, channel: String ->
|
|
52
|
+
val ctx = appContext.reactContext
|
|
53
|
+
?: throw IllegalStateException("Umeng.preInit: no React context")
|
|
54
|
+
try {
|
|
55
|
+
UMConfigure.preInit(ctx, appKey, channel)
|
|
56
|
+
Log.d(TAG, "preInit done (appKey=$appKey, channel=$channel)")
|
|
57
|
+
} catch (e: Throwable) {
|
|
58
|
+
Log.e(TAG, "preInit failed", e)
|
|
59
|
+
throw e
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Function("init") { appKey: String, channel: String, deviceType: Int, pushSecret: String ->
|
|
64
|
+
val ctx = appContext.reactContext
|
|
65
|
+
?: throw IllegalStateException("Umeng.init: no React context")
|
|
66
|
+
try {
|
|
67
|
+
UMConfigure.init(ctx, appKey, channel, deviceType, pushSecret)
|
|
68
|
+
initialized = true
|
|
69
|
+
// Once initialized, also start the current session immediately so the
|
|
70
|
+
// first foreground tick (already past) doesn't get lost.
|
|
71
|
+
MobclickAgent.onResume(ctx)
|
|
72
|
+
Log.d(TAG, "init done (appKey=$appKey, channel=$channel, deviceType=$deviceType)")
|
|
73
|
+
} catch (e: Throwable) {
|
|
74
|
+
Log.e(TAG, "init failed", e)
|
|
75
|
+
throw e
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Function("isInitialized") { initialized }
|
|
80
|
+
|
|
81
|
+
// ---- Events ----
|
|
82
|
+
Function("onEvent") { eventId: String, properties: Map<String, String> ->
|
|
83
|
+
val ctx = appContext.reactContext ?: return@Function
|
|
84
|
+
if (!initialized) {
|
|
85
|
+
Log.w(TAG, "onEvent before init — dropped (eventId=$eventId)")
|
|
86
|
+
return@Function
|
|
87
|
+
}
|
|
88
|
+
val payload: MutableMap<String, Any> = HashMap(properties.size + 1)
|
|
89
|
+
payload.putAll(properties)
|
|
90
|
+
MobclickAgent.onEventObject(ctx, eventId, payload)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---- Profile ----
|
|
94
|
+
Function("onProfileSignIn") { userId: String, provider: String? ->
|
|
95
|
+
if (!initialized) return@Function
|
|
96
|
+
if (provider.isNullOrEmpty()) {
|
|
97
|
+
MobclickAgent.onProfileSignIn(userId)
|
|
98
|
+
} else {
|
|
99
|
+
MobclickAgent.onProfileSignIn(provider, userId)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Function("onProfileSignOff") {
|
|
104
|
+
if (initialized) MobclickAgent.onProfileSignOff()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---- Manual page tracking ----
|
|
108
|
+
Function("onPageStart") { pageName: String ->
|
|
109
|
+
if (initialized) MobclickAgent.onPageStart(pageName)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Function("onPageEnd") { pageName: String ->
|
|
113
|
+
if (initialized) MobclickAgent.onPageEnd(pageName)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---- Error reporting ----
|
|
117
|
+
Function("reportError") { message: String ->
|
|
118
|
+
val ctx = appContext.reactContext ?: return@Function
|
|
119
|
+
if (!initialized) return@Function
|
|
120
|
+
MobclickAgent.reportError(ctx, message)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./plugin/build/withUmeng').default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NativeModule } from 'expo-modules-core';
|
|
2
|
+
export interface UmengNativeModuleType extends NativeModule {
|
|
3
|
+
setLogEnabled(enabled: boolean): void;
|
|
4
|
+
preInit(appKey: string, channel: string): void;
|
|
5
|
+
init(appKey: string, channel: string, deviceType: number, pushSecret: string): void;
|
|
6
|
+
isInitialized(): boolean;
|
|
7
|
+
onEvent(eventId: string, properties: Record<string, string>): void;
|
|
8
|
+
onProfileSignIn(userId: string, provider: string | null): void;
|
|
9
|
+
onProfileSignOff(): void;
|
|
10
|
+
onPageStart(pageName: string): void;
|
|
11
|
+
onPageEnd(pageName: string): void;
|
|
12
|
+
reportError(message: string): void;
|
|
13
|
+
}
|
|
14
|
+
declare const _default: UmengNativeModuleType;
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=UmengNativeModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UmengNativeModule.d.ts","sourceRoot":"","sources":["../src/UmengNativeModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG9E,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACtC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACpF,aAAa,IAAI,OAAO,CAAC;IACzB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACnE,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC/D,gBAAgB,IAAI,IAAI,CAAC;IACzB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;;AAuBD,wBAAkE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { requireOptionalNativeModule } from 'expo-modules-core';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
const stub = {
|
|
4
|
+
// expo-modules-core's NativeModule augments with addListener/removeListeners;
|
|
5
|
+
// we only declare the methods we actually expose.
|
|
6
|
+
addListener: () => { },
|
|
7
|
+
removeListeners: () => { },
|
|
8
|
+
setLogEnabled: () => { },
|
|
9
|
+
preInit: () => { },
|
|
10
|
+
init: () => { },
|
|
11
|
+
isInitialized: () => false,
|
|
12
|
+
onEvent: () => { },
|
|
13
|
+
onProfileSignIn: () => { },
|
|
14
|
+
onProfileSignOff: () => { },
|
|
15
|
+
onPageStart: () => { },
|
|
16
|
+
onPageEnd: () => { },
|
|
17
|
+
reportError: () => { },
|
|
18
|
+
};
|
|
19
|
+
const native = requireOptionalNativeModule('Umeng');
|
|
20
|
+
// Native module is now available on both iOS and Android.
|
|
21
|
+
// Fallback to stub only on web or if native autolinking failed.
|
|
22
|
+
export default (Platform.OS === 'web' || !native) ? stub : native;
|
|
23
|
+
//# sourceMappingURL=UmengNativeModule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UmengNativeModule.js","sourceRoot":"","sources":["../src/UmengNativeModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAgB,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAexC,MAAM,IAAI,GAA0B;IAClC,8EAA8E;IAC9E,kDAAkD;IAClD,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,eAAe,EAAE,GAAG,EAAE,GAAE,CAAC;IACzB,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC;IACvB,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;IACjB,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,aAAa,EAAE,GAAG,EAAE,CAAC,KAAK;IAC1B,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;IACjB,eAAe,EAAE,GAAG,EAAE,GAAE,CAAC;IACzB,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;IAC1B,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC;IACnB,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;CACc,CAAC;AAEtC,MAAM,MAAM,GAAG,2BAA2B,CAAwB,OAAO,CAAC,CAAC;AAE3E,0DAA0D;AAC1D,gEAAgE;AAChE,eAAe,CAAC,QAAQ,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC","sourcesContent":["import { requireOptionalNativeModule, NativeModule } from 'expo-modules-core';\nimport { Platform } from 'react-native';\n\nexport interface UmengNativeModuleType extends NativeModule {\n setLogEnabled(enabled: boolean): void;\n preInit(appKey: string, channel: string): void;\n init(appKey: string, channel: string, deviceType: number, pushSecret: string): void;\n isInitialized(): boolean;\n onEvent(eventId: string, properties: Record<string, string>): void;\n onProfileSignIn(userId: string, provider: string | null): void;\n onProfileSignOff(): void;\n onPageStart(pageName: string): void;\n onPageEnd(pageName: string): void;\n reportError(message: string): void;\n}\n\nconst stub: UmengNativeModuleType = {\n // expo-modules-core's NativeModule augments with addListener/removeListeners;\n // we only declare the methods we actually expose.\n addListener: () => {},\n removeListeners: () => {},\n setLogEnabled: () => {},\n preInit: () => {},\n init: () => {},\n isInitialized: () => false,\n onEvent: () => {},\n onProfileSignIn: () => {},\n onProfileSignOff: () => {},\n onPageStart: () => {},\n onPageEnd: () => {},\n reportError: () => {},\n} as unknown as UmengNativeModuleType;\n\nconst native = requireOptionalNativeModule<UmengNativeModuleType>('Umeng');\n\n// Native module is now available on both iOS and Android.\n// Fallback to stub only on web or if native autolinking failed.\nexport default (Platform.OS === 'web' || !native) ? stub : native;\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for the Umeng analytics wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Two-stage initialization to satisfy China privacy-policy rules:
|
|
5
|
+
* 1. `preInit(appKey, channel)` — call early; collects nothing personal.
|
|
6
|
+
* 2. `init(appKey, channel, opts)` — call after the user accepts your privacy policy.
|
|
7
|
+
*
|
|
8
|
+
* Session events (`onResume` / `onPause`) are wired automatically through the
|
|
9
|
+
* native `ReactActivityLifecycleListener`, so you only need to send custom
|
|
10
|
+
* events via `onEvent`.
|
|
11
|
+
*
|
|
12
|
+
* On iOS every method is a no-op (Umeng iOS is intentionally out of scope here).
|
|
13
|
+
*/
|
|
14
|
+
export type UmengDeviceType = 'phone' | 'box';
|
|
15
|
+
export interface UmengInitOptions {
|
|
16
|
+
/** Push secret. Pass empty string when push is not used. */
|
|
17
|
+
pushSecret?: string;
|
|
18
|
+
/** `'phone'` (default) or `'box'`. */
|
|
19
|
+
deviceType?: UmengDeviceType;
|
|
20
|
+
/** Enable verbose Umeng SDK logs. Off by default. */
|
|
21
|
+
logEnabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface UmengEventProperties {
|
|
24
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Turn the Umeng SDK's internal log on/off. Call before `init` to log init. */
|
|
27
|
+
export declare function setLogEnabled(enabled: boolean): void;
|
|
28
|
+
/**
|
|
29
|
+
* Pre-initialization. Safe to call before the user agrees to the privacy
|
|
30
|
+
* policy: only registers the app key and channel, does not collect or upload.
|
|
31
|
+
*/
|
|
32
|
+
export declare function preInit(appKey: string, channel?: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Full initialization. Call after the user has accepted your privacy policy.
|
|
35
|
+
* Safe to call multiple times — the SDK guards against double-init.
|
|
36
|
+
*/
|
|
37
|
+
export declare function init(appKey: string, channel?: string, options?: UmengInitOptions): void;
|
|
38
|
+
/** Returns true once `init()` has run successfully. */
|
|
39
|
+
export declare function isInitialized(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Track a custom event.
|
|
42
|
+
*
|
|
43
|
+
* @param eventId Umeng event id you configured in the Umeng dashboard.
|
|
44
|
+
* @param properties Optional key/value attributes (values are coerced to string).
|
|
45
|
+
*/
|
|
46
|
+
export declare function onEvent(eventId: string, properties?: UmengEventProperties): void;
|
|
47
|
+
/** Tag the current session with an authenticated user id. */
|
|
48
|
+
export declare function onProfileSignIn(userId: string, provider?: string): void;
|
|
49
|
+
/** Drop the previously tagged user id from the session. */
|
|
50
|
+
export declare function onProfileSignOff(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Manual page-tracking helpers. Only useful when you also call
|
|
53
|
+
* `setPageCollectionMode('manual')`. Most apps using RN won't need them.
|
|
54
|
+
*/
|
|
55
|
+
export declare function onPageStart(pageName: string): void;
|
|
56
|
+
export declare function onPageEnd(pageName: string): void;
|
|
57
|
+
/** Report a caught error message (Umeng surfaces these as custom errors). */
|
|
58
|
+
export declare function reportError(message: string): void;
|
|
59
|
+
declare const Umeng: {
|
|
60
|
+
setLogEnabled: typeof setLogEnabled;
|
|
61
|
+
preInit: typeof preInit;
|
|
62
|
+
init: typeof init;
|
|
63
|
+
isInitialized: typeof isInitialized;
|
|
64
|
+
onEvent: typeof onEvent;
|
|
65
|
+
onProfileSignIn: typeof onProfileSignIn;
|
|
66
|
+
onProfileSignOff: typeof onProfileSignOff;
|
|
67
|
+
onPageStart: typeof onPageStart;
|
|
68
|
+
onPageEnd: typeof onPageEnd;
|
|
69
|
+
reportError: typeof reportError;
|
|
70
|
+
};
|
|
71
|
+
export default Umeng;
|
|
72
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,KAAK,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,qDAAqD;IACrD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;CAC7D;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEpD;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,MAAgB,GAAG,IAAI,CAGvE;AAED;;;GAGG;AACH,wBAAgB,IAAI,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAAgB,EACzB,OAAO,GAAE,gBAAqB,GAC7B,IAAI,CASN;AAED,uDAAuD;AACvD,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,oBAAoB,GAChC,IAAI,CAWN;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAGvE;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGlD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGhD;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGjD;AAED,QAAA,MAAM,KAAK;;;;;;;;;;;CAWV,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Native from './UmengNativeModule';
|
|
2
|
+
/** Turn the Umeng SDK's internal log on/off. Call before `init` to log init. */
|
|
3
|
+
export function setLogEnabled(enabled) {
|
|
4
|
+
Native.setLogEnabled(enabled);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Pre-initialization. Safe to call before the user agrees to the privacy
|
|
8
|
+
* policy: only registers the app key and channel, does not collect or upload.
|
|
9
|
+
*/
|
|
10
|
+
export function preInit(appKey, channel = 'Umeng') {
|
|
11
|
+
if (!appKey)
|
|
12
|
+
throw new Error('[Umeng] preInit: appKey is required');
|
|
13
|
+
Native.preInit(appKey, channel);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Full initialization. Call after the user has accepted your privacy policy.
|
|
17
|
+
* Safe to call multiple times — the SDK guards against double-init.
|
|
18
|
+
*/
|
|
19
|
+
export function init(appKey, channel = 'Umeng', options = {}) {
|
|
20
|
+
if (!appKey)
|
|
21
|
+
throw new Error('[Umeng] init: appKey is required');
|
|
22
|
+
if (options.logEnabled != null)
|
|
23
|
+
Native.setLogEnabled(!!options.logEnabled);
|
|
24
|
+
Native.init(appKey, channel, options.deviceType === 'box' ? 2 : 1, options.pushSecret ?? '');
|
|
25
|
+
}
|
|
26
|
+
/** Returns true once `init()` has run successfully. */
|
|
27
|
+
export function isInitialized() {
|
|
28
|
+
return Native.isInitialized();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Track a custom event.
|
|
32
|
+
*
|
|
33
|
+
* @param eventId Umeng event id you configured in the Umeng dashboard.
|
|
34
|
+
* @param properties Optional key/value attributes (values are coerced to string).
|
|
35
|
+
*/
|
|
36
|
+
export function onEvent(eventId, properties) {
|
|
37
|
+
if (!eventId)
|
|
38
|
+
throw new Error('[Umeng] onEvent: eventId is required');
|
|
39
|
+
const map = {};
|
|
40
|
+
if (properties) {
|
|
41
|
+
for (const k of Object.keys(properties)) {
|
|
42
|
+
const v = properties[k];
|
|
43
|
+
if (v == null)
|
|
44
|
+
continue;
|
|
45
|
+
map[k] = String(v);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
Native.onEvent(eventId, map);
|
|
49
|
+
}
|
|
50
|
+
/** Tag the current session with an authenticated user id. */
|
|
51
|
+
export function onProfileSignIn(userId, provider) {
|
|
52
|
+
if (!userId)
|
|
53
|
+
throw new Error('[Umeng] onProfileSignIn: userId is required');
|
|
54
|
+
Native.onProfileSignIn(userId, provider ?? null);
|
|
55
|
+
}
|
|
56
|
+
/** Drop the previously tagged user id from the session. */
|
|
57
|
+
export function onProfileSignOff() {
|
|
58
|
+
Native.onProfileSignOff();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Manual page-tracking helpers. Only useful when you also call
|
|
62
|
+
* `setPageCollectionMode('manual')`. Most apps using RN won't need them.
|
|
63
|
+
*/
|
|
64
|
+
export function onPageStart(pageName) {
|
|
65
|
+
if (!pageName)
|
|
66
|
+
throw new Error('[Umeng] onPageStart: pageName is required');
|
|
67
|
+
Native.onPageStart(pageName);
|
|
68
|
+
}
|
|
69
|
+
export function onPageEnd(pageName) {
|
|
70
|
+
if (!pageName)
|
|
71
|
+
throw new Error('[Umeng] onPageEnd: pageName is required');
|
|
72
|
+
Native.onPageEnd(pageName);
|
|
73
|
+
}
|
|
74
|
+
/** Report a caught error message (Umeng surfaces these as custom errors). */
|
|
75
|
+
export function reportError(message) {
|
|
76
|
+
if (!message)
|
|
77
|
+
return;
|
|
78
|
+
Native.reportError(message);
|
|
79
|
+
}
|
|
80
|
+
const Umeng = {
|
|
81
|
+
setLogEnabled,
|
|
82
|
+
preInit,
|
|
83
|
+
init,
|
|
84
|
+
isInitialized,
|
|
85
|
+
onEvent,
|
|
86
|
+
onProfileSignIn,
|
|
87
|
+
onProfileSignOff,
|
|
88
|
+
onPageStart,
|
|
89
|
+
onPageEnd,
|
|
90
|
+
reportError,
|
|
91
|
+
};
|
|
92
|
+
export default Umeng;
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,qBAAqB,CAAC;AA+BzC,gFAAgF;AAChF,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,UAAkB,OAAO;IAC/D,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACpE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAClB,MAAc,EACd,UAAkB,OAAO,EACzB,UAA4B,EAAE;IAE9B,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjE,IAAI,OAAO,CAAC,UAAU,IAAI,IAAI;QAAE,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,MAAM,CAAC,IAAI,CACT,MAAM,EACN,OAAO,EACP,OAAO,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACpC,OAAO,CAAC,UAAU,IAAI,EAAE,CACzB,CAAC;AACJ,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CACrB,OAAe,EACf,UAAiC;IAEjC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACtE,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,CAAC,IAAI,IAAI;gBAAE,SAAS;YACxB,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,QAAiB;IAC/D,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAC5E,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,gBAAgB;IAC9B,MAAM,CAAC,gBAAgB,EAAE,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC5E,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC1E,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,KAAK,GAAG;IACZ,aAAa;IACb,OAAO;IACP,IAAI;IACJ,aAAa;IACb,OAAO;IACP,eAAe;IACf,gBAAgB;IAChB,WAAW;IACX,SAAS;IACT,WAAW;CACZ,CAAC;AAEF,eAAe,KAAK,CAAC","sourcesContent":["import Native from './UmengNativeModule';\n\n/**\n * Public API for the Umeng analytics wrapper.\n *\n * Two-stage initialization to satisfy China privacy-policy rules:\n * 1. `preInit(appKey, channel)` — call early; collects nothing personal.\n * 2. `init(appKey, channel, opts)` — call after the user accepts your privacy policy.\n *\n * Session events (`onResume` / `onPause`) are wired automatically through the\n * native `ReactActivityLifecycleListener`, so you only need to send custom\n * events via `onEvent`.\n *\n * On iOS every method is a no-op (Umeng iOS is intentionally out of scope here).\n */\n\nexport type UmengDeviceType = 'phone' | 'box';\n\nexport interface UmengInitOptions {\n /** Push secret. Pass empty string when push is not used. */\n pushSecret?: string;\n /** `'phone'` (default) or `'box'`. */\n deviceType?: UmengDeviceType;\n /** Enable verbose Umeng SDK logs. Off by default. */\n logEnabled?: boolean;\n}\n\nexport interface UmengEventProperties {\n [key: string]: string | number | boolean | null | undefined;\n}\n\n/** Turn the Umeng SDK's internal log on/off. Call before `init` to log init. */\nexport function setLogEnabled(enabled: boolean): void {\n Native.setLogEnabled(enabled);\n}\n\n/**\n * Pre-initialization. Safe to call before the user agrees to the privacy\n * policy: only registers the app key and channel, does not collect or upload.\n */\nexport function preInit(appKey: string, channel: string = 'Umeng'): void {\n if (!appKey) throw new Error('[Umeng] preInit: appKey is required');\n Native.preInit(appKey, channel);\n}\n\n/**\n * Full initialization. Call after the user has accepted your privacy policy.\n * Safe to call multiple times — the SDK guards against double-init.\n */\nexport function init(\n appKey: string,\n channel: string = 'Umeng',\n options: UmengInitOptions = {},\n): void {\n if (!appKey) throw new Error('[Umeng] init: appKey is required');\n if (options.logEnabled != null) Native.setLogEnabled(!!options.logEnabled);\n Native.init(\n appKey,\n channel,\n options.deviceType === 'box' ? 2 : 1,\n options.pushSecret ?? '',\n );\n}\n\n/** Returns true once `init()` has run successfully. */\nexport function isInitialized(): boolean {\n return Native.isInitialized();\n}\n\n/**\n * Track a custom event.\n *\n * @param eventId Umeng event id you configured in the Umeng dashboard.\n * @param properties Optional key/value attributes (values are coerced to string).\n */\nexport function onEvent(\n eventId: string,\n properties?: UmengEventProperties,\n): void {\n if (!eventId) throw new Error('[Umeng] onEvent: eventId is required');\n const map: Record<string, string> = {};\n if (properties) {\n for (const k of Object.keys(properties)) {\n const v = properties[k];\n if (v == null) continue;\n map[k] = String(v);\n }\n }\n Native.onEvent(eventId, map);\n}\n\n/** Tag the current session with an authenticated user id. */\nexport function onProfileSignIn(userId: string, provider?: string): void {\n if (!userId) throw new Error('[Umeng] onProfileSignIn: userId is required');\n Native.onProfileSignIn(userId, provider ?? null);\n}\n\n/** Drop the previously tagged user id from the session. */\nexport function onProfileSignOff(): void {\n Native.onProfileSignOff();\n}\n\n/**\n * Manual page-tracking helpers. Only useful when you also call\n * `setPageCollectionMode('manual')`. Most apps using RN won't need them.\n */\nexport function onPageStart(pageName: string): void {\n if (!pageName) throw new Error('[Umeng] onPageStart: pageName is required');\n Native.onPageStart(pageName);\n}\n\nexport function onPageEnd(pageName: string): void {\n if (!pageName) throw new Error('[Umeng] onPageEnd: pageName is required');\n Native.onPageEnd(pageName);\n}\n\n/** Report a caught error message (Umeng surfaces these as custom errors). */\nexport function reportError(message: string): void {\n if (!message) return;\n Native.reportError(message);\n}\n\nconst Umeng = {\n setLogEnabled,\n preInit,\n init,\n isInitialized,\n onEvent,\n onProfileSignIn,\n onProfileSignOff,\n onPageStart,\n onPageEnd,\n reportError,\n};\n\nexport default Umeng;\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 = 'ReactNativeUmeng'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage'] || 'https://github.com/yinminqian/react-native-umeng'
|
|
13
|
+
s.platforms = { :ios => '15.1' }
|
|
14
|
+
s.swift_version = '5.9'
|
|
15
|
+
s.source = { git: 'https://github.com/yinminqian/react-native-umeng.git' }
|
|
16
|
+
s.static_framework = true
|
|
17
|
+
|
|
18
|
+
s.dependency 'ExpoModulesCore'
|
|
19
|
+
s.dependency 'UMCCommon', '~> 2.1.1'
|
|
20
|
+
s.dependency 'UMCAnalytics', '~> 6.0.5'
|
|
21
|
+
|
|
22
|
+
s.pod_target_xcconfig = {
|
|
23
|
+
'DEFINES_MODULE' => 'YES',
|
|
24
|
+
'SWIFT_COMPILATION_MODE' => 'wholemodule',
|
|
25
|
+
'OTHER_LDFLAGS' => '$(inherited) -ObjC'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
s.source_files = '**/*.{h,m,swift}'
|
|
29
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// UmengBridge.h
|
|
3
|
+
// ReactNativeUmeng
|
|
4
|
+
//
|
|
5
|
+
// Objective-C bridge that imports UMCommon and UMAnalytics frameworks
|
|
6
|
+
// and re-exposes their API to Swift through a plain ObjC interface.
|
|
7
|
+
// (Old 2019 Umeng frameworks don't have Swift module maps.)
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
#import <Foundation/Foundation.h>
|
|
11
|
+
|
|
12
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
13
|
+
|
|
14
|
+
@interface UmengBridge : NSObject
|
|
15
|
+
|
|
16
|
+
+ (void)setLogEnabled:(BOOL)enabled;
|
|
17
|
+
+ (void)setupWithAppKey:(NSString *)appKey channel:(NSString *)channel;
|
|
18
|
+
+ (void)event:(NSString *)eventId;
|
|
19
|
+
+ (void)event:(NSString *)eventId attributes:(NSDictionary<NSString *, NSString *> *)attributes;
|
|
20
|
+
+ (void)profileSignInWithPUID:(NSString *)puid;
|
|
21
|
+
+ (void)profileSignInWithPUID:(NSString *)puid provider:(NSString *)provider;
|
|
22
|
+
+ (void)profileSignOff;
|
|
23
|
+
+ (void)beginLogPageView:(NSString *)pageName;
|
|
24
|
+
+ (void)endLogPageView:(NSString *)pageName;
|
|
25
|
+
|
|
26
|
+
@end
|
|
27
|
+
|
|
28
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//
|
|
2
|
+
// UmengBridge.m
|
|
3
|
+
// ReactNativeUmeng
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
#import "UmengBridge.h"
|
|
7
|
+
#import <UMCommon/UMConfigure.h>
|
|
8
|
+
#import <UMCommon/UMCommon.h>
|
|
9
|
+
#import <UMAnalytics/MobClick.h>
|
|
10
|
+
|
|
11
|
+
@implementation UmengBridge
|
|
12
|
+
|
|
13
|
+
+ (void)setLogEnabled:(BOOL)enabled {
|
|
14
|
+
[UMConfigure setLogEnabled:enabled];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
+ (void)setupWithAppKey:(NSString *)appKey channel:(NSString *)channel {
|
|
18
|
+
[UMConfigure initWithAppkey:appKey channel:channel];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
+ (void)event:(NSString *)eventId {
|
|
22
|
+
[MobClick event:eventId];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
+ (void)event:(NSString *)eventId attributes:(NSDictionary<NSString *, NSString *> *)attributes {
|
|
26
|
+
[MobClick event:eventId attributes:attributes];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
+ (void)profileSignInWithPUID:(NSString *)puid {
|
|
30
|
+
[MobClick profileSignInWithPUID:puid];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
+ (void)profileSignInWithPUID:(NSString *)puid provider:(NSString *)provider {
|
|
34
|
+
[MobClick profileSignInWithPUID:puid provider:provider];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
+ (void)profileSignOff {
|
|
38
|
+
[MobClick profileSignOff];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
+ (void)beginLogPageView:(NSString *)pageName {
|
|
42
|
+
[MobClick beginLogPageView:pageName];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
+ (void)endLogPageView:(NSString *)pageName {
|
|
46
|
+
[MobClick endLogPageView:pageName];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
// UMCommon and UMAnalytics are old Objective-C frameworks (2019) without
|
|
4
|
+
// Swift module maps. We call them via UmengBridge (ObjC wrapper in this pod).
|
|
5
|
+
|
|
6
|
+
public class UmengModule: Module {
|
|
7
|
+
private var initialized = false
|
|
8
|
+
|
|
9
|
+
public func definition() -> ModuleDefinition {
|
|
10
|
+
Name("Umeng")
|
|
11
|
+
|
|
12
|
+
Function("setLogEnabled") { (enabled: Bool) in
|
|
13
|
+
UmengBridge.setLogEnabled(enabled)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Function("preInit") { (_: String, _: String) in
|
|
17
|
+
// Umeng iOS has no separate preInit; this is a no-op.
|
|
18
|
+
// The Android privacy-policy two-stage pattern doesn't apply on iOS.
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Function("init") { (appKey: String, channel: String, _: Int, _: String) in
|
|
22
|
+
UmengBridge.setup(withAppKey: appKey, channel: channel)
|
|
23
|
+
self.initialized = true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Function("isInitialized") { () -> Bool in
|
|
27
|
+
return self.initialized
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Function("onEvent") { (eventId: String, properties: [String: String]) in
|
|
31
|
+
if properties.isEmpty {
|
|
32
|
+
UmengBridge.event(eventId)
|
|
33
|
+
} else {
|
|
34
|
+
UmengBridge.event(eventId, attributes: properties)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Function("onProfileSignIn") { (userId: String, provider: String?) in
|
|
39
|
+
if let p = provider, !p.isEmpty {
|
|
40
|
+
UmengBridge.profileSignIn(withPUID: userId, provider: p)
|
|
41
|
+
} else {
|
|
42
|
+
UmengBridge.profileSignIn(withPUID: userId)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Function("onProfileSignOff") {
|
|
47
|
+
UmengBridge.profileSignOff()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Function("onPageStart") { (pageName: String) in
|
|
51
|
+
UmengBridge.beginLogPageView(pageName)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Function("onPageEnd") { (pageName: String) in
|
|
55
|
+
UmengBridge.endLogPageView(pageName)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Function("reportError") { (message: String) in
|
|
59
|
+
// UMCAnalytics 6.0 has no stock error API; fall back to a custom event
|
|
60
|
+
UmengBridge.event("rn_reported_error", attributes: ["message": message])
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minqian/react-native-umeng",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Umeng Analytics SDK wrapper for Expo / React Native — Android only, lazy init, privacy-policy friendly",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"build",
|
|
9
|
+
"android",
|
|
10
|
+
"ios",
|
|
11
|
+
"src",
|
|
12
|
+
"plugin/build",
|
|
13
|
+
"app.plugin.js",
|
|
14
|
+
"expo-module.config.json",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "expo-module build",
|
|
19
|
+
"build:plugin": "tsc --project plugin/tsconfig.json",
|
|
20
|
+
"clean": "expo-module clean",
|
|
21
|
+
"lint": "expo-module lint",
|
|
22
|
+
"prepare": "expo-module prepare && npm run build:plugin",
|
|
23
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
24
|
+
"expo-module": "expo-module"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react-native",
|
|
28
|
+
"expo",
|
|
29
|
+
"umeng",
|
|
30
|
+
"analytics",
|
|
31
|
+
"react-native-umeng"
|
|
32
|
+
],
|
|
33
|
+
"author": "yinminqian",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/yinminqian/react-native-umeng.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/yinminqian/react-native-umeng#readme",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/react": "~19.1.1",
|
|
42
|
+
"expo": "^55.0.24",
|
|
43
|
+
"expo-module-scripts": "^55.0.2",
|
|
44
|
+
"typescript": "^5.4.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"expo": "*",
|
|
48
|
+
"react": "*",
|
|
49
|
+
"react-native": "*"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@expo/config-plugins": "^55.0.9"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ConfigPlugin } from '@expo/config-plugins';
|
|
2
|
+
export interface WithUmengProps {
|
|
3
|
+
/** The Umeng AppKey, also written as `UMENG_APPKEY` meta-data into AndroidManifest. */
|
|
4
|
+
appKey: string;
|
|
5
|
+
/** Channel name reported to Umeng. Defaults to `Umeng`. */
|
|
6
|
+
channel?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const withUmeng: ConfigPlugin<WithUmengProps>;
|
|
9
|
+
export { withUmeng };
|
|
10
|
+
declare const _default: ConfigPlugin<WithUmengProps>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withUmeng = void 0;
|
|
4
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
5
|
+
const PLUGIN_NAME = '@minqian/react-native-umeng';
|
|
6
|
+
const PLUGIN_VERSION = '0.1.0';
|
|
7
|
+
const ALIYUN_MAVEN = 'https://maven.aliyun.com/repository/public';
|
|
8
|
+
function setMetaData(application, name, value) {
|
|
9
|
+
var _a;
|
|
10
|
+
application['meta-data'] = (_a = application['meta-data']) !== null && _a !== void 0 ? _a : [];
|
|
11
|
+
const existing = application['meta-data'].find((m) => m.$ && m.$['android:name'] === name);
|
|
12
|
+
if (existing) {
|
|
13
|
+
existing.$['android:value'] = value;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
application['meta-data'].push({
|
|
17
|
+
$: {
|
|
18
|
+
'android:name': name,
|
|
19
|
+
'android:value': value,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function withUmengAndroidManifest(config, props) {
|
|
25
|
+
return (0, config_plugins_1.withAndroidManifest)(config, (c) => {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
const manifest = c.modResults.manifest;
|
|
28
|
+
manifest.application = (_a = manifest.application) !== null && _a !== void 0 ? _a : [{}];
|
|
29
|
+
const app = manifest.application[0];
|
|
30
|
+
setMetaData(app, 'UMENG_APPKEY', props.appKey);
|
|
31
|
+
setMetaData(app, 'UMENG_CHANNEL', (_b = props.channel) !== null && _b !== void 0 ? _b : 'Umeng');
|
|
32
|
+
return c;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Insert the aliyun Maven mirror at the top of `allprojects { repositories { } }`
|
|
37
|
+
* in the root `build.gradle` so the Umeng artifacts resolve in mainland China.
|
|
38
|
+
*/
|
|
39
|
+
function withUmengMavenRepo(config) {
|
|
40
|
+
return (0, config_plugins_1.withProjectBuildGradle)(config, (c) => {
|
|
41
|
+
if (c.modResults.language !== 'groovy') {
|
|
42
|
+
return c;
|
|
43
|
+
}
|
|
44
|
+
let contents = c.modResults.contents;
|
|
45
|
+
if (contents.includes(ALIYUN_MAVEN)) {
|
|
46
|
+
return c;
|
|
47
|
+
}
|
|
48
|
+
const repoBlock = ` maven { url '${ALIYUN_MAVEN}' }`;
|
|
49
|
+
// Try to inject into allprojects { repositories { ... } }
|
|
50
|
+
const allProjects = /allprojects\s*\{[\s\S]*?repositories\s*\{/m;
|
|
51
|
+
if (allProjects.test(contents)) {
|
|
52
|
+
contents = contents.replace(allProjects, (m) => `${m}\n${repoBlock}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// No allprojects.repositories block? Append one.
|
|
56
|
+
contents += `\nallprojects {\n repositories {\n${repoBlock}\n }\n}\n`;
|
|
57
|
+
}
|
|
58
|
+
c.modResults.contents = contents;
|
|
59
|
+
return c;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* UMCCommon and UMCAnalytics (Umeng legacy 2019 SDKs) ship device-only arm64 slices.
|
|
64
|
+
* They pre-date arm64 simulator support (Xcode 12+). Exclude arm64 from simulator
|
|
65
|
+
* builds on the main app target to avoid linker errors on Apple Silicon Macs.
|
|
66
|
+
* Also inject the fix into the Podfile post_install for the pod targets.
|
|
67
|
+
*/
|
|
68
|
+
function withUmengExcludedArchs(config) {
|
|
69
|
+
// 1. Main Xcode project: set "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64.
|
|
70
|
+
// The key must be wrapped in literal double quotes — CocoaPods' pbxproj parser
|
|
71
|
+
// rejects unquoted keys containing brackets, while Xcode itself accepts both.
|
|
72
|
+
const KEY = '"EXCLUDED_ARCHS[sdk=iphonesimulator*]"';
|
|
73
|
+
config = (0, config_plugins_1.withXcodeProject)(config, (c) => {
|
|
74
|
+
const project = c.modResults;
|
|
75
|
+
const targetName = c.modRequest.projectName;
|
|
76
|
+
const configurations = project.pbxXCBuildConfigurationSection();
|
|
77
|
+
for (const uuid in configurations) {
|
|
78
|
+
const conf = configurations[uuid];
|
|
79
|
+
if (conf.buildSettings &&
|
|
80
|
+
(conf.buildSettings.PRODUCT_NAME === targetName ||
|
|
81
|
+
conf.buildSettings.PRODUCT_BUNDLE_IDENTIFIER)) {
|
|
82
|
+
// Strip any previous unquoted form to avoid duplicates
|
|
83
|
+
delete conf.buildSettings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'];
|
|
84
|
+
if (!conf.buildSettings[KEY]) {
|
|
85
|
+
conf.buildSettings[KEY] = '"arm64"';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return c;
|
|
90
|
+
});
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
const withUmeng = (config, props) => {
|
|
94
|
+
if (!(props === null || props === void 0 ? void 0 : props.appKey)) {
|
|
95
|
+
throw new Error(`[${PLUGIN_NAME}] "appKey" prop is required`);
|
|
96
|
+
}
|
|
97
|
+
config = withUmengAndroidManifest(config, props);
|
|
98
|
+
config = withUmengMavenRepo(config);
|
|
99
|
+
config = withUmengExcludedArchs(config);
|
|
100
|
+
return config;
|
|
101
|
+
};
|
|
102
|
+
exports.withUmeng = withUmeng;
|
|
103
|
+
exports.default = (0, config_plugins_1.createRunOncePlugin)(withUmeng, PLUGIN_NAME, PLUGIN_VERSION);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { requireOptionalNativeModule, NativeModule } from 'expo-modules-core';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface UmengNativeModuleType extends NativeModule {
|
|
5
|
+
setLogEnabled(enabled: boolean): void;
|
|
6
|
+
preInit(appKey: string, channel: string): void;
|
|
7
|
+
init(appKey: string, channel: string, deviceType: number, pushSecret: string): void;
|
|
8
|
+
isInitialized(): boolean;
|
|
9
|
+
onEvent(eventId: string, properties: Record<string, string>): void;
|
|
10
|
+
onProfileSignIn(userId: string, provider: string | null): void;
|
|
11
|
+
onProfileSignOff(): void;
|
|
12
|
+
onPageStart(pageName: string): void;
|
|
13
|
+
onPageEnd(pageName: string): void;
|
|
14
|
+
reportError(message: string): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const stub: UmengNativeModuleType = {
|
|
18
|
+
// expo-modules-core's NativeModule augments with addListener/removeListeners;
|
|
19
|
+
// we only declare the methods we actually expose.
|
|
20
|
+
addListener: () => {},
|
|
21
|
+
removeListeners: () => {},
|
|
22
|
+
setLogEnabled: () => {},
|
|
23
|
+
preInit: () => {},
|
|
24
|
+
init: () => {},
|
|
25
|
+
isInitialized: () => false,
|
|
26
|
+
onEvent: () => {},
|
|
27
|
+
onProfileSignIn: () => {},
|
|
28
|
+
onProfileSignOff: () => {},
|
|
29
|
+
onPageStart: () => {},
|
|
30
|
+
onPageEnd: () => {},
|
|
31
|
+
reportError: () => {},
|
|
32
|
+
} as unknown as UmengNativeModuleType;
|
|
33
|
+
|
|
34
|
+
const native = requireOptionalNativeModule<UmengNativeModuleType>('Umeng');
|
|
35
|
+
|
|
36
|
+
// Native module is now available on both iOS and Android.
|
|
37
|
+
// Fallback to stub only on web or if native autolinking failed.
|
|
38
|
+
export default (Platform.OS === 'web' || !native) ? stub : native;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import Native from './UmengNativeModule';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public API for the Umeng analytics wrapper.
|
|
5
|
+
*
|
|
6
|
+
* Two-stage initialization to satisfy China privacy-policy rules:
|
|
7
|
+
* 1. `preInit(appKey, channel)` — call early; collects nothing personal.
|
|
8
|
+
* 2. `init(appKey, channel, opts)` — call after the user accepts your privacy policy.
|
|
9
|
+
*
|
|
10
|
+
* Session events (`onResume` / `onPause`) are wired automatically through the
|
|
11
|
+
* native `ReactActivityLifecycleListener`, so you only need to send custom
|
|
12
|
+
* events via `onEvent`.
|
|
13
|
+
*
|
|
14
|
+
* On iOS every method is a no-op (Umeng iOS is intentionally out of scope here).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type UmengDeviceType = 'phone' | 'box';
|
|
18
|
+
|
|
19
|
+
export interface UmengInitOptions {
|
|
20
|
+
/** Push secret. Pass empty string when push is not used. */
|
|
21
|
+
pushSecret?: string;
|
|
22
|
+
/** `'phone'` (default) or `'box'`. */
|
|
23
|
+
deviceType?: UmengDeviceType;
|
|
24
|
+
/** Enable verbose Umeng SDK logs. Off by default. */
|
|
25
|
+
logEnabled?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UmengEventProperties {
|
|
29
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Turn the Umeng SDK's internal log on/off. Call before `init` to log init. */
|
|
33
|
+
export function setLogEnabled(enabled: boolean): void {
|
|
34
|
+
Native.setLogEnabled(enabled);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pre-initialization. Safe to call before the user agrees to the privacy
|
|
39
|
+
* policy: only registers the app key and channel, does not collect or upload.
|
|
40
|
+
*/
|
|
41
|
+
export function preInit(appKey: string, channel: string = 'Umeng'): void {
|
|
42
|
+
if (!appKey) throw new Error('[Umeng] preInit: appKey is required');
|
|
43
|
+
Native.preInit(appKey, channel);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Full initialization. Call after the user has accepted your privacy policy.
|
|
48
|
+
* Safe to call multiple times — the SDK guards against double-init.
|
|
49
|
+
*/
|
|
50
|
+
export function init(
|
|
51
|
+
appKey: string,
|
|
52
|
+
channel: string = 'Umeng',
|
|
53
|
+
options: UmengInitOptions = {},
|
|
54
|
+
): void {
|
|
55
|
+
if (!appKey) throw new Error('[Umeng] init: appKey is required');
|
|
56
|
+
if (options.logEnabled != null) Native.setLogEnabled(!!options.logEnabled);
|
|
57
|
+
Native.init(
|
|
58
|
+
appKey,
|
|
59
|
+
channel,
|
|
60
|
+
options.deviceType === 'box' ? 2 : 1,
|
|
61
|
+
options.pushSecret ?? '',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Returns true once `init()` has run successfully. */
|
|
66
|
+
export function isInitialized(): boolean {
|
|
67
|
+
return Native.isInitialized();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Track a custom event.
|
|
72
|
+
*
|
|
73
|
+
* @param eventId Umeng event id you configured in the Umeng dashboard.
|
|
74
|
+
* @param properties Optional key/value attributes (values are coerced to string).
|
|
75
|
+
*/
|
|
76
|
+
export function onEvent(
|
|
77
|
+
eventId: string,
|
|
78
|
+
properties?: UmengEventProperties,
|
|
79
|
+
): void {
|
|
80
|
+
if (!eventId) throw new Error('[Umeng] onEvent: eventId is required');
|
|
81
|
+
const map: Record<string, string> = {};
|
|
82
|
+
if (properties) {
|
|
83
|
+
for (const k of Object.keys(properties)) {
|
|
84
|
+
const v = properties[k];
|
|
85
|
+
if (v == null) continue;
|
|
86
|
+
map[k] = String(v);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
Native.onEvent(eventId, map);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Tag the current session with an authenticated user id. */
|
|
93
|
+
export function onProfileSignIn(userId: string, provider?: string): void {
|
|
94
|
+
if (!userId) throw new Error('[Umeng] onProfileSignIn: userId is required');
|
|
95
|
+
Native.onProfileSignIn(userId, provider ?? null);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Drop the previously tagged user id from the session. */
|
|
99
|
+
export function onProfileSignOff(): void {
|
|
100
|
+
Native.onProfileSignOff();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Manual page-tracking helpers. Only useful when you also call
|
|
105
|
+
* `setPageCollectionMode('manual')`. Most apps using RN won't need them.
|
|
106
|
+
*/
|
|
107
|
+
export function onPageStart(pageName: string): void {
|
|
108
|
+
if (!pageName) throw new Error('[Umeng] onPageStart: pageName is required');
|
|
109
|
+
Native.onPageStart(pageName);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function onPageEnd(pageName: string): void {
|
|
113
|
+
if (!pageName) throw new Error('[Umeng] onPageEnd: pageName is required');
|
|
114
|
+
Native.onPageEnd(pageName);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Report a caught error message (Umeng surfaces these as custom errors). */
|
|
118
|
+
export function reportError(message: string): void {
|
|
119
|
+
if (!message) return;
|
|
120
|
+
Native.reportError(message);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const Umeng = {
|
|
124
|
+
setLogEnabled,
|
|
125
|
+
preInit,
|
|
126
|
+
init,
|
|
127
|
+
isInitialized,
|
|
128
|
+
onEvent,
|
|
129
|
+
onProfileSignIn,
|
|
130
|
+
onProfileSignOff,
|
|
131
|
+
onPageStart,
|
|
132
|
+
onPageEnd,
|
|
133
|
+
reportError,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default Umeng;
|