@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/lib/client.js ADDED
@@ -0,0 +1,511 @@
1
+ "use strict";
2
+ // The KissMyApps app client, with no React Native in it: the app's storage, device and
3
+ // network come in through `Environment`, so the same code runs in tests and in Node.
4
+ // The Android and iOS SDKs follow the same rules; see integrations/analytics-mobile.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.KmaClient = exports.EVENT_NAME = exports.DEFAULT_ENDPOINT = exports.SDK_VERSION = void 0;
7
+ exports.uuid = uuid;
8
+ exports.SDK_VERSION = '1.0.0';
9
+ exports.DEFAULT_ENDPOINT = 'https://events.kissmyapps.dev';
10
+ exports.EVENT_NAME = /^[A-Za-z][A-Za-z0-9_.:-]{0,79}$/;
11
+ const KEY = /^kma_pk_(live|test)_[A-Za-z0-9]{20,64}$/;
12
+ const BATCH_SIZE = 50;
13
+ const FLUSH_AT = 30;
14
+ const HEARTBEAT_MS = 60000;
15
+ const MAX_PROPERTIES_BYTES = 32000;
16
+ function uuid() {
17
+ // Hermes has no crypto.randomUUID; a polyfill may add it.
18
+ const random = globalThis.crypto;
19
+ if (random === null || random === void 0 ? void 0 : random.randomUUID)
20
+ return random.randomUUID();
21
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
22
+ const r = (Math.random() * 16) | 0;
23
+ return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
24
+ });
25
+ }
26
+ class KmaClient {
27
+ constructor(options, env) {
28
+ var _a, _b;
29
+ this.queue = [];
30
+ this.screenName = null;
31
+ this.foregroundAt = 0;
32
+ this.loaded = false;
33
+ this.waiting = [];
34
+ this.flushTimer = null;
35
+ this.heartbeatTimer = null;
36
+ this.saveTimer = null;
37
+ this.inflight = null;
38
+ this.failures = 0;
39
+ this.lastSent = 0;
40
+ this.identityPending = false;
41
+ this.inForeground = false;
42
+ this.firstLaunch = false;
43
+ this.stopped = false;
44
+ if (!KEY.test((_a = options.key) !== null && _a !== void 0 ? _a : '')) {
45
+ throw new Error('KissMyApps: init needs a client key that starts with kma_pk_');
46
+ }
47
+ this.options = {
48
+ ...options,
49
+ endpoint: ((_b = options.endpoint) !== null && _b !== void 0 ? _b : exports.DEFAULT_ENDPOINT).replace(/\/+$/, ''),
50
+ };
51
+ this.env = env;
52
+ this.storageKey = `kma:${options.key.slice(-8)}`;
53
+ this.state = {
54
+ anonymousId: uuid(),
55
+ userId: null,
56
+ traits: null,
57
+ session: null,
58
+ optedOut: false,
59
+ };
60
+ this.ready = this.load();
61
+ }
62
+ // Public API ---------------------------------------------------------------------------
63
+ /** Records something a person did. */
64
+ track(name, properties = {}) {
65
+ this.whenLoaded(() => {
66
+ if (this.stopped || this.state.optedOut)
67
+ return;
68
+ if (!exports.EVENT_NAME.test(name)) {
69
+ this.warn(`"${name}" is not a valid event name: use letters, numbers and _ . : - (up to 80).`);
70
+ return;
71
+ }
72
+ this.enqueue(name, properties, this.ensureSession());
73
+ });
74
+ }
75
+ /** Records a screen view; later events carry the screen's name. */
76
+ screen(name, properties = {}) {
77
+ const screen = String(name !== null && name !== void 0 ? name : '')
78
+ .trim()
79
+ .slice(0, 300);
80
+ if (!screen)
81
+ return this.warn('screen needs a name.');
82
+ this.whenLoaded(() => {
83
+ if (this.stopped || this.state.optedOut)
84
+ return;
85
+ const previous = this.screenName;
86
+ this.screenName = screen;
87
+ const session = this.ensureSession();
88
+ if (!session.firstScreen) {
89
+ session.firstScreen = screen;
90
+ this.save();
91
+ }
92
+ this.enqueue('screen_viewed', { name: screen, ...(previous ? { previous } : {}), ...properties }, session);
93
+ });
94
+ }
95
+ /** Ties this install to your own user ID, with optional traits such as email, name or plan. */
96
+ identify(userId, traits) {
97
+ const id = String(userId !== null && userId !== void 0 ? userId : '').trim();
98
+ if (!id || id.length > 200)
99
+ return this.warn('identify needs a user ID of 1 to 200 characters.');
100
+ this.whenLoaded(() => {
101
+ var _a;
102
+ this.state.userId = id;
103
+ if (traits)
104
+ this.state.traits = { ...((_a = this.state.traits) !== null && _a !== void 0 ? _a : {}), ...this.clean(traits) };
105
+ this.identityPending = true;
106
+ this.save();
107
+ this.scheduleFlush(0);
108
+ });
109
+ }
110
+ /** Updates traits for the current person without changing who they are. */
111
+ setTraits(traits) {
112
+ this.whenLoaded(() => {
113
+ var _a;
114
+ this.state.traits = { ...((_a = this.state.traits) !== null && _a !== void 0 ? _a : {}), ...this.clean(traits) };
115
+ this.identityPending = true;
116
+ this.save();
117
+ this.scheduleFlush(0);
118
+ });
119
+ }
120
+ /** Forgets the user, for example on sign out: what follows is a new person and session. */
121
+ reset() {
122
+ this.whenLoaded(() => {
123
+ void this.flush();
124
+ this.state.userId = null;
125
+ this.state.traits = null;
126
+ this.identityPending = false;
127
+ this.state.anonymousId = uuid();
128
+ this.state.session = null;
129
+ this.save();
130
+ if (this.inForeground)
131
+ this.ensureSession();
132
+ });
133
+ }
134
+ /** Stops recording and drops anything not sent yet, until optIn. Remembered across launches. */
135
+ optOut() {
136
+ this.whenLoaded(() => {
137
+ this.state.optedOut = true;
138
+ this.queue = [];
139
+ this.save();
140
+ });
141
+ }
142
+ optIn() {
143
+ this.whenLoaded(() => {
144
+ this.state.optedOut = false;
145
+ this.save();
146
+ });
147
+ }
148
+ getAnonymousId() {
149
+ return this.state.anonymousId;
150
+ }
151
+ getUserId() {
152
+ return this.state.userId;
153
+ }
154
+ getSessionId() {
155
+ var _a, _b;
156
+ return (_b = (_a = this.state.session) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
157
+ }
158
+ /** Sends everything queued now. */
159
+ flush() {
160
+ if (!this.loaded)
161
+ return this.ready.then(() => this.flush());
162
+ if (this.inflight)
163
+ return this.inflight;
164
+ this.inflight = this.sendQueued().finally(() => {
165
+ this.inflight = null;
166
+ });
167
+ return this.inflight;
168
+ }
169
+ /** Stops timers; the client records nothing more. */
170
+ shutdown() {
171
+ this.stopped = true;
172
+ this.inForeground = false;
173
+ if (this.flushTimer)
174
+ clearTimeout(this.flushTimer);
175
+ if (this.heartbeatTimer)
176
+ clearInterval(this.heartbeatTimer);
177
+ if (this.saveTimer) {
178
+ clearTimeout(this.saveTimer);
179
+ void this.writeNow();
180
+ }
181
+ }
182
+ // App lifecycle, called by the platform layer ------------------------------------------
183
+ /** The app came to the front. The first call is the launch. */
184
+ foreground() {
185
+ this.whenLoaded(() => {
186
+ var _a;
187
+ if (this.stopped || this.inForeground)
188
+ return;
189
+ const launch = this.foregroundAt === 0;
190
+ this.inForeground = true;
191
+ this.foregroundAt = this.now();
192
+ if (this.state.optedOut)
193
+ return;
194
+ const session = this.ensureSession();
195
+ if (this.options.trackLifecycle !== false) {
196
+ if (launch)
197
+ this.recordInstallOrUpdate(session);
198
+ this.enqueue('app_opened', { from_background: !launch }, session);
199
+ }
200
+ (_a = this.heartbeatTimer) !== null && _a !== void 0 ? _a : (this.heartbeatTimer = setInterval(() => this.heartbeat(), HEARTBEAT_MS));
201
+ });
202
+ }
203
+ /** The app went to the background: record it and send what is queued while it can. */
204
+ background() {
205
+ this.whenLoaded(() => {
206
+ if (this.stopped || !this.inForeground)
207
+ return;
208
+ this.inForeground = false;
209
+ if (this.heartbeatTimer)
210
+ clearInterval(this.heartbeatTimer);
211
+ this.heartbeatTimer = null;
212
+ if (this.state.optedOut)
213
+ return;
214
+ const session = this.state.session;
215
+ if (session && this.options.trackLifecycle !== false) {
216
+ const seconds = Math.round((this.now() - this.foregroundAt) / 1000);
217
+ this.enqueue('app_backgrounded', { seconds_in_foreground: Math.max(0, seconds) }, session);
218
+ }
219
+ if (session)
220
+ session.last = this.now();
221
+ void this.writeNow();
222
+ void this.flush();
223
+ });
224
+ }
225
+ // State --------------------------------------------------------------------------------
226
+ async load() {
227
+ var _a, _b, _c;
228
+ const storage = this.env.storage;
229
+ if (!storage)
230
+ this.warn('No storage was given, so each launch counts as a new install.');
231
+ let saved = null;
232
+ let queue = [];
233
+ try {
234
+ const raw = await (storage === null || storage === void 0 ? void 0 : storage.getItem(this.storageKey));
235
+ saved = raw ? JSON.parse(raw) : null;
236
+ const rawQueue = await (storage === null || storage === void 0 ? void 0 : storage.getItem(`${this.storageKey}:queue`));
237
+ queue = rawQueue ? JSON.parse(rawQueue) : [];
238
+ }
239
+ catch {
240
+ saved = null;
241
+ }
242
+ if (saved === null || saved === void 0 ? void 0 : saved.anonymousId) {
243
+ this.state = {
244
+ anonymousId: saved.anonymousId,
245
+ userId: (_a = saved.userId) !== null && _a !== void 0 ? _a : null,
246
+ traits: (_b = saved.traits) !== null && _b !== void 0 ? _b : null,
247
+ session: (_c = saved.session) !== null && _c !== void 0 ? _c : null,
248
+ optedOut: saved.optedOut === true,
249
+ version: saved.version,
250
+ build: saved.build,
251
+ };
252
+ }
253
+ // Nothing saved means a first launch, or the app's data was cleared.
254
+ this.firstLaunch = !(saved === null || saved === void 0 ? void 0 : saved.anonymousId);
255
+ this.queue = Array.isArray(queue) ? queue : [];
256
+ this.identityPending = Boolean(this.state.traits);
257
+ this.loaded = true;
258
+ // What an earlier launch could not send goes now.
259
+ if (this.queue.length)
260
+ this.scheduleFlush(0);
261
+ for (const run of this.waiting.splice(0))
262
+ run();
263
+ }
264
+ whenLoaded(run) {
265
+ if (this.loaded)
266
+ run();
267
+ else
268
+ this.waiting.push(run);
269
+ }
270
+ recordInstallOrUpdate(session) {
271
+ const version = this.options.appVersion;
272
+ const build = this.options.build;
273
+ if (this.firstLaunch) {
274
+ this.enqueue('app_installed', this.clean({ version, build }), session);
275
+ }
276
+ else if ((version && this.state.version && version !== this.state.version) ||
277
+ (build && this.state.build && build !== this.state.build)) {
278
+ this.enqueue('app_updated', this.clean({
279
+ version,
280
+ build,
281
+ previous_version: this.state.version,
282
+ previous_build: this.state.build,
283
+ }), session);
284
+ }
285
+ this.firstLaunch = false;
286
+ this.state.version = version !== null && version !== void 0 ? version : this.state.version;
287
+ this.state.build = build !== null && build !== void 0 ? build : this.state.build;
288
+ this.save();
289
+ }
290
+ save() {
291
+ if (this.saveTimer)
292
+ return;
293
+ this.saveTimer = setTimeout(() => {
294
+ this.saveTimer = null;
295
+ void this.writeNow();
296
+ }, 250);
297
+ }
298
+ async writeNow() {
299
+ var _a, _b;
300
+ if (this.saveTimer) {
301
+ clearTimeout(this.saveTimer);
302
+ this.saveTimer = null;
303
+ }
304
+ try {
305
+ await ((_a = this.env.storage) === null || _a === void 0 ? void 0 : _a.setItem(this.storageKey, JSON.stringify(this.state)));
306
+ await ((_b = this.env.storage) === null || _b === void 0 ? void 0 : _b.setItem(`${this.storageKey}:queue`, JSON.stringify(this.queue)));
307
+ }
308
+ catch {
309
+ // Storage full or failing: the client keeps working in memory.
310
+ }
311
+ }
312
+ // Sessions and events -------------------------------------------------------------------
313
+ now() {
314
+ var _a, _b, _c;
315
+ return (_c = (_b = (_a = this.env).now) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : Date.now();
316
+ }
317
+ ensureSession() {
318
+ var _a, _b;
319
+ const now = this.now();
320
+ const timeout = ((_a = this.options.sessionTimeoutMinutes) !== null && _a !== void 0 ? _a : 30) * 60000;
321
+ let session = this.state.session;
322
+ if (!session || now - session.last > timeout) {
323
+ session = { id: uuid(), started: now, last: now, firstScreen: (_b = this.screenName) !== null && _b !== void 0 ? _b : undefined };
324
+ this.state.session = session;
325
+ this.enqueue('session_started', {}, session);
326
+ }
327
+ else {
328
+ session.last = now;
329
+ }
330
+ return session;
331
+ }
332
+ enqueue(name, properties, session) {
333
+ var _a, _b, _c;
334
+ this.queue.push({
335
+ name,
336
+ insert_id: uuid(),
337
+ time: new Date(this.now()).toISOString(),
338
+ screen: (_a = this.screenName) !== null && _a !== void 0 ? _a : undefined,
339
+ properties: this.clean(properties),
340
+ user_id: this.state.userId,
341
+ anonymous_id: this.state.anonymousId,
342
+ session_id: session.id,
343
+ session_started_at: new Date(session.started).toISOString(),
344
+ });
345
+ const max = (_b = this.options.maxQueue) !== null && _b !== void 0 ? _b : 1000;
346
+ if (this.queue.length > max)
347
+ this.queue.splice(0, this.queue.length - max);
348
+ this.save();
349
+ this.scheduleFlush(this.queue.length >= FLUSH_AT ? 0 : ((_c = this.options.flushIntervalSeconds) !== null && _c !== void 0 ? _c : 10) * 1000);
350
+ }
351
+ clean(properties) {
352
+ try {
353
+ const json = JSON.stringify(properties !== null && properties !== void 0 ? properties : {});
354
+ if (json.length > MAX_PROPERTIES_BYTES) {
355
+ this.warn('Event properties are over 32 KB and were dropped.');
356
+ return {};
357
+ }
358
+ const parsed = JSON.parse(json);
359
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
360
+ }
361
+ catch {
362
+ return {};
363
+ }
364
+ }
365
+ // Sending ------------------------------------------------------------------------------
366
+ context(session) {
367
+ const device = this.env.device();
368
+ return {
369
+ platform: device.platform,
370
+ sdk: this.env.sdk,
371
+ timezone: device.timezone,
372
+ language: device.language,
373
+ device_type: device.device_type,
374
+ device_model: device.device_model,
375
+ os_name: device.os_name,
376
+ os_version: device.os_version,
377
+ app_version: this.options.appVersion,
378
+ screen_width: Math.round(device.screen_width),
379
+ screen_height: Math.round(device.screen_height),
380
+ page_path: session === null || session === void 0 ? void 0 : session.firstScreen,
381
+ };
382
+ }
383
+ /** One request's body: queued events share a person and session; with none, a heartbeat. */
384
+ body(events) {
385
+ var _a, _b, _c;
386
+ const head = events[0];
387
+ const session = this.state.session;
388
+ const sessionId = (_a = head === null || head === void 0 ? void 0 : head.session_id) !== null && _a !== void 0 ? _a : session === null || session === void 0 ? void 0 : session.id;
389
+ const startedAt = (_b = head === null || head === void 0 ? void 0 : head.session_started_at) !== null && _b !== void 0 ? _b : (session ? new Date(session.started).toISOString() : undefined);
390
+ const live = (session === null || session === void 0 ? void 0 : session.id) === sessionId ? session : null;
391
+ const current = !head || head.anonymous_id === this.state.anonymousId;
392
+ const userId = current ? this.state.userId : events[events.length - 1].user_id;
393
+ const body = {
394
+ anonymous_id: (_c = head === null || head === void 0 ? void 0 : head.anonymous_id) !== null && _c !== void 0 ? _c : this.state.anonymousId,
395
+ session: sessionId ? { id: sessionId, started_at: startedAt } : undefined,
396
+ sent_at: new Date(this.now()).toISOString(),
397
+ context: this.context(live),
398
+ events: events.map(({ name, insert_id, time, screen, properties, user_id }) => ({
399
+ name,
400
+ insert_id,
401
+ time,
402
+ ...(screen ? { screen } : {}),
403
+ properties,
404
+ user_id,
405
+ })),
406
+ };
407
+ if (userId)
408
+ body.user_id = userId;
409
+ if (current && this.state.traits)
410
+ body.traits = this.state.traits;
411
+ return JSON.parse(JSON.stringify(body));
412
+ }
413
+ nextBatch() {
414
+ const head = this.queue[0];
415
+ if (!head)
416
+ return [];
417
+ const batch = [];
418
+ for (const event of this.queue) {
419
+ if (event.session_id !== head.session_id ||
420
+ event.anonymous_id !== head.anonymous_id ||
421
+ batch.length >= BATCH_SIZE)
422
+ break;
423
+ batch.push(event);
424
+ }
425
+ return batch;
426
+ }
427
+ async post(body) {
428
+ return this.env.fetch(`${this.options.endpoint}/v1/events`, {
429
+ method: 'POST',
430
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${this.options.key}` },
431
+ body: JSON.stringify(body),
432
+ });
433
+ }
434
+ async sendQueued() {
435
+ while (!this.stopped && !this.state.optedOut) {
436
+ const batch = this.nextBatch();
437
+ if (!batch.length && !(this.identityPending && this.state.session))
438
+ return;
439
+ const traitsSent = this.state.traits;
440
+ const carriesIdentity = !batch.length || batch[0].anonymous_id === this.state.anonymousId;
441
+ let response;
442
+ try {
443
+ response = await this.post(this.body(batch));
444
+ }
445
+ catch {
446
+ this.retryLater();
447
+ return;
448
+ }
449
+ const refused = response.status >= 400 &&
450
+ response.status < 500 &&
451
+ response.status !== 408 &&
452
+ response.status !== 429;
453
+ if (!response.ok && !refused) {
454
+ this.retryLater();
455
+ return;
456
+ }
457
+ if (refused)
458
+ this.warn(`Events were refused (${response.status}): ${await response.text().catch(() => '')}`);
459
+ const sent = new Set(batch.map((event) => event.insert_id));
460
+ this.queue = this.queue.filter((event) => !sent.has(event.insert_id));
461
+ if (response.ok && carriesIdentity && this.state.traits === traitsSent) {
462
+ this.state.traits = null;
463
+ this.identityPending = false;
464
+ }
465
+ this.failures = 0;
466
+ this.lastSent = this.now();
467
+ this.save();
468
+ }
469
+ }
470
+ retryLater() {
471
+ this.failures += 1;
472
+ this.scheduleFlush(Math.min(60000, 1000 * 2 ** Math.min(this.failures, 6)), true);
473
+ }
474
+ scheduleFlush(delay, replace = false) {
475
+ if (this.stopped)
476
+ return;
477
+ if (this.flushTimer) {
478
+ if (delay > 0 && !replace)
479
+ return;
480
+ clearTimeout(this.flushTimer);
481
+ }
482
+ this.flushTimer = setTimeout(() => {
483
+ this.flushTimer = null;
484
+ void this.flush();
485
+ }, delay);
486
+ }
487
+ /** Keeps an open app counted as online. */
488
+ heartbeat() {
489
+ const session = this.state.session;
490
+ if (this.stopped || this.state.optedOut || !this.inForeground || !session)
491
+ return;
492
+ session.last = this.now();
493
+ if (this.queue.length || this.identityPending) {
494
+ void this.flush();
495
+ return;
496
+ }
497
+ if (this.now() - this.lastSent < HEARTBEAT_MS - 1000)
498
+ return;
499
+ this.post(this.body([]))
500
+ .then(() => {
501
+ this.lastSent = this.now();
502
+ })
503
+ .catch(() => undefined);
504
+ }
505
+ warn(message) {
506
+ var _a;
507
+ if (this.options.debug)
508
+ ((_a = this.env.log) !== null && _a !== void 0 ? _a : console.warn)(`KissMyApps: ${message}`);
509
+ }
510
+ }
511
+ exports.KmaClient = KmaClient;
@@ -0,0 +1,2 @@
1
+ import type { DeviceInfo } from './client';
2
+ export declare function deviceInfo(): DeviceInfo;
package/lib/device.js ADDED
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ // What React Native itself can tell about the device, with no native module to link.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.deviceInfo = deviceInfo;
5
+ const react_native_1 = require("react-native");
6
+ function timezone() {
7
+ try {
8
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
9
+ }
10
+ catch {
11
+ return 'UTC';
12
+ }
13
+ }
14
+ function language() {
15
+ var _a, _b, _c, _d, _e, _f, _g;
16
+ const fromSettings = react_native_1.Platform.OS === 'ios'
17
+ ? ((_c = (_b = (_a = react_native_1.NativeModules.SettingsManager) === null || _a === void 0 ? void 0 : _a.settings) === null || _b === void 0 ? void 0 : _b.AppleLocale) !== null && _c !== void 0 ? _c : (_f = (_e = (_d = react_native_1.NativeModules.SettingsManager) === null || _d === void 0 ? void 0 : _d.settings) === null || _e === void 0 ? void 0 : _e.AppleLanguages) === null || _f === void 0 ? void 0 : _f[0])
18
+ : (_g = react_native_1.NativeModules.I18nManager) === null || _g === void 0 ? void 0 : _g.localeIdentifier;
19
+ if (typeof fromSettings === 'string' && fromSettings)
20
+ return fromSettings.replace(/_/g, '-').split('@')[0];
21
+ try {
22
+ return Intl.DateTimeFormat().resolvedOptions().locale || 'en';
23
+ }
24
+ catch {
25
+ return 'en';
26
+ }
27
+ }
28
+ function deviceInfo() {
29
+ var _a, _b, _c;
30
+ const screen = react_native_1.Dimensions.get('screen');
31
+ const shortSide = Math.min(screen.width, screen.height);
32
+ const constants = ((_a = react_native_1.Platform.constants) !== null && _a !== void 0 ? _a : {});
33
+ if (react_native_1.Platform.OS === 'ios') {
34
+ const pad = react_native_1.Platform.isPad === true || constants.interfaceIdiom === 'pad';
35
+ return {
36
+ platform: 'ios',
37
+ os_name: pad ? 'iPadOS' : 'iOS',
38
+ os_version: String((_b = constants.osVersion) !== null && _b !== void 0 ? _b : react_native_1.Platform.Version),
39
+ device_type: pad ? 'tablet' : 'mobile',
40
+ device_model: pad ? 'iPad' : 'iPhone',
41
+ screen_width: screen.width,
42
+ screen_height: screen.height,
43
+ timezone: timezone(),
44
+ language: language(),
45
+ };
46
+ }
47
+ const model = typeof constants.Model === 'string' ? constants.Model : undefined;
48
+ return {
49
+ platform: 'android',
50
+ os_name: 'Android',
51
+ os_version: String((_c = constants.Release) !== null && _c !== void 0 ? _c : react_native_1.Platform.Version),
52
+ device_type: shortSide >= 600 ? 'tablet' : 'mobile',
53
+ device_model: model,
54
+ screen_width: screen.width,
55
+ screen_height: screen.height,
56
+ timezone: timezone(),
57
+ language: language(),
58
+ };
59
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { KmaClient } from './client';
2
+ import type { ClientOptions, Properties, Storage } from './client';
3
+ import type { NavigationRefLike } from './navigation';
4
+ export { KmaClient, SDK_VERSION } from './client';
5
+ export type { ClientOptions, DeviceInfo, Properties, Storage } from './client';
6
+ export type { NavigationRefLike } from './navigation';
7
+ export interface InitOptions extends ClientOptions {
8
+ /** Where the SDK keeps its IDs and offline queue, such as AsyncStorage. */
9
+ storage?: Storage;
10
+ }
11
+ export declare const KissMyApps: {
12
+ /** Starts the SDK once, as early as possible, with the project's client key. */
13
+ init(options: InitOptions): KmaClient;
14
+ track(name: string, properties?: Properties): void;
15
+ screen(name: string, properties?: Properties): void;
16
+ identify(userId: string | number, traits?: Properties): void;
17
+ setTraits(traits: Properties): void;
18
+ reset(): void;
19
+ flush(): Promise<void>;
20
+ optOut(): void;
21
+ optIn(): void;
22
+ getAnonymousId(): string | null;
23
+ getSessionId(): string | null;
24
+ };
25
+ /**
26
+ * Screen views from React Navigation:
27
+ * `<NavigationContainer ref={ref} {...trackNavigation(ref)}>`.
28
+ */
29
+ export declare function trackNavigation(ref: NavigationRefLike): {
30
+ onReady: () => void;
31
+ onStateChange: () => void;
32
+ };
33
+ export default KissMyApps;