@kissmyappss/react-native 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KissMyApps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,179 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <a href="https://kissmyapps.dev">
3
+ <img src="https://raw.githubusercontent.com/onorhime/kissmyapps-mobile/main/.github/assets/kissmyapps.png" width="88" height="88" alt="KissMyApps">
4
+ </a>
5
+ </p>
2
6
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
7
+ <h1 align="center">KissMyApps for React Native</h1>
8
+
9
+ <p align="center">
10
+ Installs, sessions, screens and your own events, from your app to your KissMyApps dashboard.<br>
11
+ Plain JavaScript: nothing to link, works with Expo.
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@kissmyappss/react-native"><img src="https://img.shields.io/npm/v/@kissmyappss/react-native?color=ff80bf&labelColor=222222" alt="npm"></a>
16
+ <img src="https://img.shields.io/badge/React%20Native-0.70%2B-ff80bf?logo=react&logoColor=white&labelColor=222222" alt="React Native 0.70+">
17
+ <img src="https://img.shields.io/badge/Expo-ready-ff80bf?logo=expo&logoColor=white&labelColor=222222" alt="Expo">
18
+ <img src="https://img.shields.io/badge/TypeScript-included-ff80bf?logo=typescript&logoColor=white&labelColor=222222" alt="TypeScript">
19
+ <img src="https://img.shields.io/badge/license-MIT-ff80bf?labelColor=222222" alt="MIT licence">
20
+ </p>
21
+
22
+ ---
23
+
24
+ ## Why this SDK
25
+
26
+ - **Start it and you are done.** Installs, updates, opens, time in the app and sessions are
27
+ recorded on their own; React Navigation screens take one line.
28
+ - **Nothing is lost offline.** Events wait in storage, survive a restart and go out with
29
+ backoff once there is a connection.
30
+ - **No native code.** Pure JavaScript with TypeScript types, so it works in Expo Go and needs
31
+ no `pod install`.
32
+ - **Respectful by default.** A random ID per install, no advertising ID, no fingerprinting,
33
+ and an opt-out that is remembered across launches.
34
+ - **One dashboard for every platform.** It sends the same events as the
35
+ [iOS and Android SDKs](https://github.com/onorhime/kissmyapps-mobile) and the web script.
36
+
37
+ ## Install
38
+
39
+ ```sh
40
+ npm install @kissmyappss/react-native @react-native-async-storage/async-storage
41
+ ```
42
+
43
+ With Expo, `npx expo install @kissmyappss/react-native @react-native-async-storage/async-storage`.
44
+
45
+ You need React Native 0.70 or later, and a **client key** from your project in the
46
+ KissMyApps console under **Settings → API keys**. Keys starting `kma_pk_live_` count as
47
+ production; keys starting `kma_pk_test_` are kept apart as development.
48
+
49
+ ## Quick start
50
+
51
+ Start the SDK once, as early as possible, for example at the top of `App.tsx`:
52
+
53
+ ```tsx
54
+ import AsyncStorage from '@react-native-async-storage/async-storage'
55
+ import { KissMyApps } from '@kissmyappss/react-native'
56
+
57
+ KissMyApps.init({
58
+ key: 'kma_pk_live_…',
59
+ storage: AsyncStorage,
60
+ appVersion: '2.4.0', // for example DeviceInfo.getVersion()
61
+ build: '120', // and DeviceInfo.getBuildNumber()
62
+ })
63
+ ```
64
+
65
+ <details>
66
+ <summary>Version and build with Expo</summary>
67
+
68
+ ```tsx
69
+ import * as Application from 'expo-application'
70
+
71
+ KissMyApps.init({
72
+ key: 'kma_pk_live_…',
73
+ storage: AsyncStorage,
74
+ appVersion: Application.nativeApplicationVersion ?? undefined,
75
+ build: Application.nativeBuildVersion ?? undefined,
76
+ })
77
+ ```
78
+
79
+ </details>
80
+
81
+ Storage is what lets the SDK recognise a returning person and keep events through a restart.
82
+ Without it the SDK still works, but every launch counts as a new install.
83
+
84
+ ## Track what matters
85
+
86
+ ### Screens
87
+
88
+ With React Navigation, pass the helper to the container and every screen is recorded under
89
+ its route name:
90
+
91
+ ```tsx
92
+ import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
93
+ import { trackNavigation } from '@kissmyappss/react-native'
94
+
95
+ export function App() {
96
+ const navigation = useNavigationContainerRef()
97
+ return (
98
+ <NavigationContainer ref={navigation} {...trackNavigation(navigation)}>
99
+ {/* your navigators */}
100
+ </NavigationContainer>
101
+ )
102
+ }
103
+ ```
104
+
105
+ Without React Navigation, call `KissMyApps.screen('Lesson')` when a screen appears.
106
+
107
+ ### Events and people
108
+
109
+ ```ts
110
+ KissMyApps.track('lesson_completed', { lesson: 3, score: 0.92 })
111
+
112
+ KissMyApps.identify('user-42', { email: 'amaka@example.com', plan: 'annual' })
113
+ KissMyApps.setTraits({ plan: 'monthly' })
114
+ KissMyApps.reset() // on sign out: what follows belongs to a new, anonymous person
115
+
116
+ KissMyApps.optOut() // stops sending, remembered across launches
117
+ KissMyApps.optIn()
118
+ await KissMyApps.flush()
119
+ ```
120
+
121
+ Event names use letters, numbers and `_ . : -`, start with a letter and are up to 80
122
+ characters; other names are ignored. Calls made before `init` are kept and run once it is
123
+ called.
124
+
125
+ ### Everything at a glance
126
+
127
+ | Call | What it does |
128
+ | ---------------------------------- | ----------------------------------------------------- |
129
+ | `KissMyApps.init(options)` | Starts recording. Call once. |
130
+ | `KissMyApps.track(name, props?)` | Records one of your events. |
131
+ | `KissMyApps.screen(name, props?)` | Records a screen view. |
132
+ | `trackNavigation(ref)` | Records every React Navigation screen. |
133
+ | `KissMyApps.identify(id, traits?)` | Ties this install to your user ID. |
134
+ | `KissMyApps.setTraits(traits)` | Updates what you know about the person. |
135
+ | `KissMyApps.reset()` | Forgets the person and starts a new anonymous one. |
136
+ | `KissMyApps.optOut()` / `optIn()` | Stops or resumes sending. Remembered across launches. |
137
+ | `KissMyApps.flush()` | Sends what is queued now; returns a promise. |
138
+ | `KissMyApps.getAnonymousId()` | This install's random ID, for support tickets. |
139
+ | `KissMyApps.getSessionId()` | The current session's ID. |
140
+
141
+ ## What gets recorded
142
+
143
+ | Event | When | Properties |
144
+ | ------------------ | ------------------------------------------ | -------------------------------------------------------- |
145
+ | `session_started` | A new session begins | – |
146
+ | `app_installed` | The first launch after install | `version`, `build` |
147
+ | `app_updated` | The first launch of a new version or build | `version`, `build`, `previous_version`, `previous_build` |
148
+ | `app_opened` | The app comes to the foreground | `from_background` |
149
+ | `app_backgrounded` | The app leaves the foreground | `seconds_in_foreground` |
150
+ | `screen_viewed` | A screen appears | `name`, `previous`, plus your own |
151
+
152
+ Every request also carries iOS or Android and the OS version, phone or tablet, the device
153
+ model on Android (iPhone or iPad on iOS), screen size, language, time zone, app version and
154
+ the SDK version. Country, region and city are worked out by the KissMyApps server from the
155
+ request's address, which is only ever kept as a salted hash. There is no advertising ID.
156
+
157
+ A session ends after 30 minutes in the background or without events. Events are sent every
158
+ 10 seconds, at once when 30 are waiting, and when the app goes to the background. While
159
+ offline they wait in storage (up to 1,000, oldest dropped first) and are retried with
160
+ backoff, including on the next launch.
161
+
162
+ ## Options
163
+
164
+ | Option | Default | What it does |
165
+ | ----------------------- | ------------------------------- | -------------------------------------------------------------- |
166
+ | `key` | Required | The project's client key. |
167
+ | `storage` | None | AsyncStorage or anything with `getItem` and `setItem`. |
168
+ | `appVersion` | None | The version shown in the dashboard. |
169
+ | `build` | None | The build number, used to spot updates. |
170
+ | `endpoint` | `https://events.kissmyapps.dev` | Where events are sent. |
171
+ | `sessionTimeoutMinutes` | `30` | Minutes in the background or idle before a new session starts. |
172
+ | `flushIntervalSeconds` | `10` | How often queued events are sent while the app is open. |
173
+ | `maxQueue` | `1000` | The most events kept while offline. |
174
+ | `trackLifecycle` | `true` | Record installs, updates, opens and backgrounding. |
175
+ | `debug` | `false` | Log refused events and other problems to the console. |
176
+
177
+ ## License
178
+
179
+ MIT © KissMyApps
@@ -0,0 +1,127 @@
1
+ export declare const SDK_VERSION = "1.0.0";
2
+ export declare const DEFAULT_ENDPOINT = "https://events.kissmyapps.dev";
3
+ export type Properties = Record<string, unknown>;
4
+ /** AsyncStorage, MMKV or anything with the same two methods. */
5
+ export interface Storage {
6
+ getItem(key: string): Promise<string | null> | string | null;
7
+ setItem(key: string, value: string): Promise<void> | void;
8
+ }
9
+ export interface DeviceInfo {
10
+ platform: 'ios' | 'android';
11
+ os_name: string;
12
+ os_version: string;
13
+ device_type: 'mobile' | 'tablet';
14
+ device_model?: string;
15
+ screen_width: number;
16
+ screen_height: number;
17
+ timezone: string;
18
+ language: string;
19
+ }
20
+ export interface ClientOptions {
21
+ /** The project's client key, kma_pk_live_… or kma_pk_test_…. */
22
+ key: string;
23
+ endpoint?: string;
24
+ /** The app's version, such as 2.4.0, and build number, such as 120. */
25
+ appVersion?: string;
26
+ build?: string;
27
+ /** A session ends after this long in the background or without events. Default 30. */
28
+ sessionTimeoutMinutes?: number;
29
+ /** How often queued events are sent while the app is open. Default 10. */
30
+ flushIntervalSeconds?: number;
31
+ /** The most events kept while offline; the oldest go first. Default 1000. */
32
+ maxQueue?: number;
33
+ /** Record installs, updates, opens and backgrounding. Default true. */
34
+ trackLifecycle?: boolean;
35
+ debug?: boolean;
36
+ }
37
+ export interface Environment {
38
+ storage: Storage | null;
39
+ device(): DeviceInfo;
40
+ fetch: (url: string, init: RequestInit) => Promise<Pick<Response, 'ok' | 'status' | 'text'>>;
41
+ sdk: string;
42
+ now?: () => number;
43
+ log?: (message: string) => void;
44
+ }
45
+ export interface QueuedEvent {
46
+ name: string;
47
+ insert_id: string;
48
+ time: string;
49
+ screen?: string;
50
+ properties: Properties;
51
+ user_id: string | null;
52
+ anonymous_id: string;
53
+ session_id: string;
54
+ session_started_at: string;
55
+ }
56
+ export declare const EVENT_NAME: RegExp;
57
+ export declare function uuid(): string;
58
+ export declare class KmaClient {
59
+ readonly options: ClientOptions & {
60
+ endpoint: string;
61
+ };
62
+ private readonly env;
63
+ private readonly storageKey;
64
+ private state;
65
+ private queue;
66
+ private screenName;
67
+ private foregroundAt;
68
+ private loaded;
69
+ private readonly waiting;
70
+ private flushTimer;
71
+ private heartbeatTimer;
72
+ private saveTimer;
73
+ private inflight;
74
+ private failures;
75
+ private lastSent;
76
+ private identityPending;
77
+ private inForeground;
78
+ private firstLaunch;
79
+ private stopped;
80
+ /** Settles once saved state is read and the launch is recorded. */
81
+ readonly ready: Promise<void>;
82
+ constructor(options: ClientOptions, env: Environment);
83
+ /** Records something a person did. */
84
+ track(name: string, properties?: Properties): void;
85
+ /** Records a screen view; later events carry the screen's name. */
86
+ screen(name: string, properties?: Properties): void;
87
+ /** Ties this install to your own user ID, with optional traits such as email, name or plan. */
88
+ identify(userId: string | number, traits?: Properties): void;
89
+ /** Updates traits for the current person without changing who they are. */
90
+ setTraits(traits: Properties): void;
91
+ /** Forgets the user, for example on sign out: what follows is a new person and session. */
92
+ reset(): void;
93
+ /** Stops recording and drops anything not sent yet, until optIn. Remembered across launches. */
94
+ optOut(): void;
95
+ optIn(): void;
96
+ getAnonymousId(): string;
97
+ getUserId(): string | null;
98
+ getSessionId(): string | null;
99
+ /** Sends everything queued now. */
100
+ flush(): Promise<void>;
101
+ /** Stops timers; the client records nothing more. */
102
+ shutdown(): void;
103
+ /** The app came to the front. The first call is the launch. */
104
+ foreground(): void;
105
+ /** The app went to the background: record it and send what is queued while it can. */
106
+ background(): void;
107
+ private load;
108
+ private whenLoaded;
109
+ private recordInstallOrUpdate;
110
+ private save;
111
+ private writeNow;
112
+ private now;
113
+ private ensureSession;
114
+ private enqueue;
115
+ private clean;
116
+ private context;
117
+ /** One request's body: queued events share a person and session; with none, a heartbeat. */
118
+ body(events: QueuedEvent[]): Properties;
119
+ private nextBatch;
120
+ private post;
121
+ private sendQueued;
122
+ private retryLater;
123
+ private scheduleFlush;
124
+ /** Keeps an open app counted as online. */
125
+ private heartbeat;
126
+ private warn;
127
+ }