@thravic/react-native 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -2
- package/dist/client.d.ts +96 -0
- package/dist/client.js +423 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +49 -0
- package/dist/react-native.d.ts +2 -0
- package/dist/react-native.js +66 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +2 -0
- package/package.json +40 -4
- package/src/client.ts +449 -0
- package/src/index.ts +56 -0
- package/src/react-native.ts +71 -0
- package/src/types.ts +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Thravic = exports.assignVariant = exports.ThravicClient = void 0;
|
|
4
|
+
exports.useVariant = useVariant;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const client_1 = require("./client");
|
|
7
|
+
const react_native_1 = require("./react-native");
|
|
8
|
+
var client_2 = require("./client");
|
|
9
|
+
Object.defineProperty(exports, "ThravicClient", { enumerable: true, get: function () { return client_2.ThravicClient; } });
|
|
10
|
+
Object.defineProperty(exports, "assignVariant", { enumerable: true, get: function () { return client_2.assignVariant; } });
|
|
11
|
+
/**
|
|
12
|
+
* Thravic for React Native.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { Thravic } from '@thravic/react-native';
|
|
16
|
+
*
|
|
17
|
+
* Thravic.init('TF-1A2B3C4D', { appVersion: '1.4.0', bundleId: 'com.acme.shop' });
|
|
18
|
+
* Thravic.trackNavigation(navigationRef); // screen views, automatically
|
|
19
|
+
* Thravic.track('purchase', { amount: 49 }); // your own events
|
|
20
|
+
* const version = await Thravic.variant('new-paywall'); // A/B tests
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
exports.Thravic = new client_1.ThravicClient(react_native_1.reactNativeEnvironment);
|
|
24
|
+
/**
|
|
25
|
+
* The version of A/B test `key` to show, for use in a component: `undefined`
|
|
26
|
+
* while it loads, then a version id or null for the original.
|
|
27
|
+
*
|
|
28
|
+
* ```tsx
|
|
29
|
+
* const version = useVariant('new-paywall');
|
|
30
|
+
* if (version === undefined) return null;
|
|
31
|
+
* return version === 'b' ? <NewPaywall /> : <Paywall />;
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function useVariant(key) {
|
|
35
|
+
const [version, setVersion] = (0, react_1.useState)(undefined);
|
|
36
|
+
(0, react_1.useEffect)(() => {
|
|
37
|
+
let live = true;
|
|
38
|
+
setVersion(undefined);
|
|
39
|
+
void exports.Thravic.variant(key).then((id) => {
|
|
40
|
+
if (live)
|
|
41
|
+
setVersion(id);
|
|
42
|
+
});
|
|
43
|
+
return () => {
|
|
44
|
+
live = false;
|
|
45
|
+
};
|
|
46
|
+
}, [key]);
|
|
47
|
+
return version;
|
|
48
|
+
}
|
|
49
|
+
exports.default = exports.Thravic;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.reactNativeEnvironment = void 0;
|
|
7
|
+
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
8
|
+
const react_native_1 = require("react-native");
|
|
9
|
+
function language() {
|
|
10
|
+
try {
|
|
11
|
+
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** What React Native itself knows about the device, with no native module to install. */
|
|
18
|
+
function device() {
|
|
19
|
+
const constants = (react_native_1.Platform.constants || {});
|
|
20
|
+
const screen = react_native_1.Dimensions.get('screen');
|
|
21
|
+
const base = {
|
|
22
|
+
screenWidth: Math.round(screen.width),
|
|
23
|
+
screenHeight: Math.round(screen.height),
|
|
24
|
+
language: language(),
|
|
25
|
+
};
|
|
26
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
27
|
+
return {
|
|
28
|
+
...base,
|
|
29
|
+
os: 'Android',
|
|
30
|
+
// `Platform.Version` is the API level on Android; Release is "14".
|
|
31
|
+
osVersion: constants.Release || String(react_native_1.Platform.Version),
|
|
32
|
+
deviceModel: [constants.Manufacturer, constants.Model].filter(Boolean).join(' ') || undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (react_native_1.Platform.OS === 'ios') {
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
os: 'iOS',
|
|
39
|
+
osVersion: String(react_native_1.Platform.Version),
|
|
40
|
+
// iOS does not expose the model without a native module.
|
|
41
|
+
deviceModel: constants.interfaceIdiom === 'pad' ? 'iPad' : 'iPhone',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { ...base, os: react_native_1.Platform.OS };
|
|
45
|
+
}
|
|
46
|
+
exports.reactNativeEnvironment = {
|
|
47
|
+
storage: async_storage_1.default,
|
|
48
|
+
device,
|
|
49
|
+
fetch: (...args) => fetch(...args),
|
|
50
|
+
now: () => Date.now(),
|
|
51
|
+
onAppStateChange(listener) {
|
|
52
|
+
// 'inactive' is iOS passing through (Control Center, a call banner): not a
|
|
53
|
+
// departure. Only 'background' ends the visit and only 'active' resumes it.
|
|
54
|
+
const subscription = react_native_1.AppState.addEventListener('change', (state) => {
|
|
55
|
+
if (state === 'active')
|
|
56
|
+
listener(true);
|
|
57
|
+
else if (state === 'background')
|
|
58
|
+
listener(false);
|
|
59
|
+
});
|
|
60
|
+
return () => subscription.remove();
|
|
61
|
+
},
|
|
62
|
+
setInterval: (fn, ms) => setInterval(fn, ms),
|
|
63
|
+
clearInterval: (handle) => clearInterval(handle),
|
|
64
|
+
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
65
|
+
log: (...args) => console.warn(...args),
|
|
66
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Options for `Thravic.init`. */
|
|
2
|
+
export interface ThravicOptions {
|
|
3
|
+
/** Where the Thravic API lives. The dashboard's install snippet fills this in. */
|
|
4
|
+
apiUrl?: string;
|
|
5
|
+
/** Your app's version, e.g. `Application.nativeApplicationVersion` (Expo) or
|
|
6
|
+
* `DeviceInfo.getVersion()` (react-native-device-info). Shown under Versions. */
|
|
7
|
+
appVersion?: string;
|
|
8
|
+
/** Your app's bundle ID, used as the host of screen URLs (`app://<bundleId>/Home`). */
|
|
9
|
+
bundleId?: string;
|
|
10
|
+
/** Minutes in the background after which the next open starts a new session. */
|
|
11
|
+
sessionTimeoutMinutes?: number;
|
|
12
|
+
/** Seconds between automatic sends of queued events. */
|
|
13
|
+
flushIntervalSeconds?: number;
|
|
14
|
+
/** Log what the SDK does to the console. */
|
|
15
|
+
debug?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** One event as `POST /api/collect/{trackingId}/batch` takes it. */
|
|
18
|
+
export interface CollectEvent {
|
|
19
|
+
eventId: string;
|
|
20
|
+
type: 'pageview' | 'custom' | 'session_end';
|
|
21
|
+
url: string;
|
|
22
|
+
visitorId: string;
|
|
23
|
+
sessionId: string;
|
|
24
|
+
screenWidth?: number;
|
|
25
|
+
screenHeight?: number;
|
|
26
|
+
language?: string;
|
|
27
|
+
os?: string;
|
|
28
|
+
osVersion?: string;
|
|
29
|
+
appVersion?: string;
|
|
30
|
+
deviceModel?: string;
|
|
31
|
+
utmSource?: string;
|
|
32
|
+
utmMedium?: string;
|
|
33
|
+
utmCampaign?: string;
|
|
34
|
+
data?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
/** A running A/B test, as `GET /api/collect/{trackingId}/config` lists it. */
|
|
37
|
+
export interface Experiment {
|
|
38
|
+
id: string;
|
|
39
|
+
/** What the app asks for: `Thravic.variant(key)`. */
|
|
40
|
+
key: string;
|
|
41
|
+
/** Share of users in the test, 0–100. */
|
|
42
|
+
traffic: number;
|
|
43
|
+
variants: Array<{
|
|
44
|
+
id: string;
|
|
45
|
+
weight: number;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/** What the device reports about itself. */
|
|
49
|
+
export interface DeviceInfo {
|
|
50
|
+
os: string;
|
|
51
|
+
osVersion?: string;
|
|
52
|
+
deviceModel?: string;
|
|
53
|
+
screenWidth?: number;
|
|
54
|
+
screenHeight?: number;
|
|
55
|
+
language?: string;
|
|
56
|
+
}
|
|
57
|
+
/** Key-value storage that survives app restarts (AsyncStorage's shape). */
|
|
58
|
+
export interface Storage {
|
|
59
|
+
getItem(key: string): Promise<string | null>;
|
|
60
|
+
setItem(key: string, value: string): Promise<void>;
|
|
61
|
+
removeItem(key: string): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
/** Everything the client needs from its platform, so it can run outside React Native. */
|
|
64
|
+
export interface Environment {
|
|
65
|
+
storage: Storage;
|
|
66
|
+
device: () => DeviceInfo;
|
|
67
|
+
fetch: typeof fetch;
|
|
68
|
+
now: () => number;
|
|
69
|
+
/** Calls `listener(true)` when the app comes to the foreground and `listener(false)`
|
|
70
|
+
* when it leaves; returns an unsubscribe. */
|
|
71
|
+
onAppStateChange: (listener: (active: boolean) => void) => () => void;
|
|
72
|
+
setInterval: (fn: () => void, ms: number) => unknown;
|
|
73
|
+
clearInterval: (handle: unknown) => void;
|
|
74
|
+
setTimeout: (fn: () => void, ms: number) => unknown;
|
|
75
|
+
log: (...args: unknown[]) => void;
|
|
76
|
+
}
|
|
77
|
+
/** The parts of a React Navigation container ref the screen tracker uses. Expo
|
|
78
|
+
* Router's `useNavigationContainerRef()` returns one too. */
|
|
79
|
+
export interface NavigationRefLike {
|
|
80
|
+
addListener(event: 'state', listener: () => void): () => void;
|
|
81
|
+
getCurrentRoute(): {
|
|
82
|
+
name: string;
|
|
83
|
+
params?: object;
|
|
84
|
+
} | undefined;
|
|
85
|
+
isReady?(): boolean;
|
|
86
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thravic/react-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Thravic analytics for React Native and Expo apps: screens, sessions, custom events and A/B tests.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"react-native": "src/index.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"src",
|
|
15
|
+
"!src/**/__tests__",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"analytics",
|
|
20
|
+
"react-native",
|
|
21
|
+
"expo",
|
|
22
|
+
"thravic"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
32
|
+
"react": ">=17.0.0",
|
|
33
|
+
"react-native": ">=0.70.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@react-native-async-storage/async-storage": "^2.1.0",
|
|
37
|
+
"@types/react": "^18.3.31",
|
|
38
|
+
"react-native": "^0.76.0",
|
|
39
|
+
"typescript": "^5.6.0",
|
|
40
|
+
"vitest": "^4.0.18"
|
|
41
|
+
}
|
|
42
|
+
}
|