@bananalytics/react-native 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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/context/app.d.ts +12 -0
  4. package/dist/context/app.js +34 -0
  5. package/dist/context/device.d.ts +12 -0
  6. package/dist/context/device.js +38 -0
  7. package/dist/context/session.d.ts +49 -0
  8. package/dist/context/session.js +99 -0
  9. package/dist/core/client.d.ts +112 -0
  10. package/dist/core/client.js +283 -0
  11. package/dist/core/config.d.ts +27 -0
  12. package/dist/core/config.js +41 -0
  13. package/dist/core/errors.d.ts +19 -0
  14. package/dist/core/errors.js +40 -0
  15. package/dist/hooks/BananalyticsProvider.d.ts +22 -0
  16. package/dist/hooks/BananalyticsProvider.js +72 -0
  17. package/dist/hooks/RochadeProvider.d.ts +22 -0
  18. package/dist/hooks/RochadeProvider.js +72 -0
  19. package/dist/hooks/useBananalytics.d.ts +16 -0
  20. package/dist/hooks/useBananalytics.js +26 -0
  21. package/dist/hooks/useRochade.d.ts +16 -0
  22. package/dist/hooks/useRochade.js +26 -0
  23. package/dist/hooks/useTrackScreen.d.ts +14 -0
  24. package/dist/hooks/useTrackScreen.js +24 -0
  25. package/dist/index.d.ts +61 -0
  26. package/dist/index.js +97 -0
  27. package/dist/privacy/consent.d.ts +39 -0
  28. package/dist/privacy/consent.js +57 -0
  29. package/dist/privacy/sanitizer.d.ts +16 -0
  30. package/dist/privacy/sanitizer.js +40 -0
  31. package/dist/tracking/event-builder.d.ts +47 -0
  32. package/dist/tracking/event-builder.js +73 -0
  33. package/dist/tracking/lifecycle-tracker.d.ts +24 -0
  34. package/dist/tracking/lifecycle-tracker.js +51 -0
  35. package/dist/tracking/screen-tracker.d.ts +20 -0
  36. package/dist/tracking/screen-tracker.js +34 -0
  37. package/dist/tracking/user-identity.d.ts +40 -0
  38. package/dist/tracking/user-identity.js +75 -0
  39. package/dist/transport/batcher.d.ts +44 -0
  40. package/dist/transport/batcher.js +91 -0
  41. package/dist/transport/persister.d.ts +49 -0
  42. package/dist/transport/persister.js +148 -0
  43. package/dist/transport/queue.d.ts +41 -0
  44. package/dist/transport/queue.js +67 -0
  45. package/dist/transport/retry.d.ts +15 -0
  46. package/dist/transport/retry.js +52 -0
  47. package/dist/transport/transport.d.ts +23 -0
  48. package/dist/transport/transport.js +51 -0
  49. package/dist/types/common.d.ts +6 -0
  50. package/dist/types/common.js +2 -0
  51. package/dist/types/config.d.ts +25 -0
  52. package/dist/types/config.js +2 -0
  53. package/dist/types/events.d.ts +53 -0
  54. package/dist/types/events.js +2 -0
  55. package/dist/utils/id.d.ts +13 -0
  56. package/dist/utils/id.js +18 -0
  57. package/dist/utils/logger.d.ts +11 -0
  58. package/dist/utils/logger.js +24 -0
  59. package/dist/utils/network.d.ts +7 -0
  60. package/dist/utils/network.js +33 -0
  61. package/dist/utils/time.d.ts +13 -0
  62. package/dist/utils/time.js +17 -0
  63. package/dist/utils/validation.d.ts +26 -0
  64. package/dist/utils/validation.js +60 -0
  65. package/package.json +64 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Configuration for the Bananalytics analytics SDK.
3
+ */
4
+ export interface BananalyticsConfig {
5
+ /** Write-only public API key. */
6
+ apiKey: string;
7
+ /** Ingestion API endpoint URL. */
8
+ endpoint: string;
9
+ /** Milliseconds between automatic flushes. @default 30000 */
10
+ flushInterval?: number;
11
+ /** Number of events that triggers an immediate flush. @default 20 */
12
+ flushAt?: number;
13
+ /** Maximum number of events held in memory. @default 1000 */
14
+ maxQueueSize?: number;
15
+ /** Maximum retry attempts for failed flushes. @default 3 */
16
+ maxRetries?: number;
17
+ /** Enable debug console logging. @default false */
18
+ debug?: boolean;
19
+ /** Auto-track app foreground/background events. @default true */
20
+ trackAppLifecycle?: boolean;
21
+ /** Auto-track screen views (requires React Navigation setup). @default false */
22
+ trackScreens?: boolean;
23
+ /** Session inactivity timeout in milliseconds. @default 1800000 (30 minutes) */
24
+ sessionTimeout?: number;
25
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,53 @@
1
+ import { Properties, Timestamp, UUID } from './common';
2
+ /** The type of analytics event. */
3
+ export type EventType = 'track' | 'screen' | 'identify';
4
+ /** Device context information. */
5
+ export interface DeviceContext {
6
+ os: string;
7
+ osVersion: string;
8
+ model: string;
9
+ manufacturer: string;
10
+ screenWidth: number;
11
+ screenHeight: number;
12
+ }
13
+ /** Application context information. */
14
+ export interface AppContext {
15
+ name: string;
16
+ version: string;
17
+ build: string;
18
+ bundleId: string;
19
+ }
20
+ /** Session context information. */
21
+ export interface SessionContext {
22
+ id: UUID;
23
+ startedAt: Timestamp;
24
+ }
25
+ /** Full event context sent with each event. */
26
+ export interface EventContext {
27
+ device: DeviceContext;
28
+ app: AppContext;
29
+ session: SessionContext;
30
+ locale: string;
31
+ timezone: string;
32
+ }
33
+ /** The payload sent to the backend for each event. */
34
+ export interface EventPayload {
35
+ event: string;
36
+ type: EventType;
37
+ properties: Properties;
38
+ context: EventContext;
39
+ userId: string | null;
40
+ anonymousId: UUID;
41
+ timestamp: Timestamp;
42
+ messageId: UUID;
43
+ }
44
+ /** Screen event tracking data. */
45
+ export interface ScreenEvent {
46
+ screenName: string;
47
+ properties?: Properties;
48
+ }
49
+ /** Identify event data. */
50
+ export interface IdentifyEvent {
51
+ userId: string;
52
+ traits?: Properties;
53
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ import { UUID } from '../types/common';
2
+ /**
3
+ * Generates a new UUID v4 identifier.
4
+ *
5
+ * @returns A unique UUID v4 string
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const id = generateId();
10
+ * // "550e8400-e29b-41d4-a716-446655440000"
11
+ * ```
12
+ */
13
+ export declare function generateId(): UUID;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateId = generateId;
4
+ const uuid_1 = require("uuid");
5
+ /**
6
+ * Generates a new UUID v4 identifier.
7
+ *
8
+ * @returns A unique UUID v4 string
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const id = generateId();
13
+ * // "550e8400-e29b-41d4-a716-446655440000"
14
+ * ```
15
+ */
16
+ function generateId() {
17
+ return (0, uuid_1.v4)();
18
+ }
@@ -0,0 +1,11 @@
1
+ /** Internal debug logger that only outputs when debug mode is enabled. */
2
+ export declare class Logger {
3
+ private enabled;
4
+ constructor(debug: boolean);
5
+ /** Log a debug message. Only outputs when debug mode is enabled. */
6
+ debug(message: string, ...args: unknown[]): void;
7
+ /** Log a warning. Always outputs regardless of debug mode. */
8
+ warn(message: string, ...args: unknown[]): void;
9
+ /** Log an error. Always outputs regardless of debug mode. */
10
+ error(message: string, ...args: unknown[]): void;
11
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
4
+ /** Internal debug logger that only outputs when debug mode is enabled. */
5
+ class Logger {
6
+ constructor(debug) {
7
+ this.enabled = debug;
8
+ }
9
+ /** Log a debug message. Only outputs when debug mode is enabled. */
10
+ debug(message, ...args) {
11
+ if (this.enabled) {
12
+ console.log(`[Bananalytics] ${message}`, ...args);
13
+ }
14
+ }
15
+ /** Log a warning. Always outputs regardless of debug mode. */
16
+ warn(message, ...args) {
17
+ console.warn(`[Bananalytics] ${message}`, ...args);
18
+ }
19
+ /** Log an error. Always outputs regardless of debug mode. */
20
+ error(message, ...args) {
21
+ console.error(`[Bananalytics] ${message}`, ...args);
22
+ }
23
+ }
24
+ exports.Logger = Logger;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checks if the device currently has network connectivity.
3
+ * Uses React Native's NetInfo if available, falls back to assuming online.
4
+ *
5
+ * @returns true if connected, false if offline
6
+ */
7
+ export declare function isOnline(): Promise<boolean>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isOnline = isOnline;
4
+ /**
5
+ * Checks if the device currently has network connectivity.
6
+ * Uses React Native's NetInfo if available, falls back to assuming online.
7
+ *
8
+ * @returns true if connected, false if offline
9
+ */
10
+ async function isOnline() {
11
+ try {
12
+ // Try React Native NetInfo (requires @react-native-community/netinfo)
13
+ const NetInfo = require('@react-native-community/netinfo');
14
+ const state = await NetInfo.fetch();
15
+ return state.isConnected ?? true;
16
+ }
17
+ catch {
18
+ // NetInfo not installed — try fetch-based check
19
+ try {
20
+ const controller = new AbortController();
21
+ const timeout = setTimeout(() => controller.abort(), 3000);
22
+ await fetch('https://clients3.google.com/generate_204', {
23
+ method: 'HEAD',
24
+ signal: controller.signal,
25
+ });
26
+ clearTimeout(timeout);
27
+ return true;
28
+ }
29
+ catch {
30
+ return true; // Assume online if we can't determine — better to try and fail
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,13 @@
1
+ import { Timestamp } from '../types/common';
2
+ /**
3
+ * Returns the current time as an ISO 8601 timestamp string.
4
+ *
5
+ * @returns ISO 8601 formatted timestamp
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const ts = now();
10
+ * // "2025-01-15T10:30:00.000Z"
11
+ * ```
12
+ */
13
+ export declare function now(): Timestamp;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.now = now;
4
+ /**
5
+ * Returns the current time as an ISO 8601 timestamp string.
6
+ *
7
+ * @returns ISO 8601 formatted timestamp
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const ts = now();
12
+ * // "2025-01-15T10:30:00.000Z"
13
+ * ```
14
+ */
15
+ function now() {
16
+ return new Date().toISOString();
17
+ }
@@ -0,0 +1,26 @@
1
+ import { Properties } from '../types/common';
2
+ /**
3
+ * Validates an event name.
4
+ *
5
+ * @param name - The event name to validate
6
+ * @returns Error message if invalid, null if valid
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * validateEventName('button_clicked'); // null
11
+ * validateEventName(''); // "event name is required"
12
+ * ```
13
+ */
14
+ export declare function validateEventName(name: string): string | null;
15
+ /**
16
+ * Validates event properties.
17
+ *
18
+ * @param properties - The properties object to validate
19
+ * @returns Error message if invalid, null if valid
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * validateProperties({ key: 'value' }); // null
24
+ * ```
25
+ */
26
+ export declare function validateProperties(properties: Properties): string | null;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateEventName = validateEventName;
4
+ exports.validateProperties = validateProperties;
5
+ const MAX_EVENT_NAME_LENGTH = 256;
6
+ const MAX_PROPERTY_KEY_LENGTH = 256;
7
+ const MAX_PROPERTY_VALUE_SIZE = 8192; // 8KB
8
+ const MAX_PROPERTIES_COUNT = 256;
9
+ const EVENT_NAME_REGEX = /^[\w$]+$/;
10
+ /**
11
+ * Validates an event name.
12
+ *
13
+ * @param name - The event name to validate
14
+ * @returns Error message if invalid, null if valid
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * validateEventName('button_clicked'); // null
19
+ * validateEventName(''); // "event name is required"
20
+ * ```
21
+ */
22
+ function validateEventName(name) {
23
+ if (!name) {
24
+ return 'event name is required';
25
+ }
26
+ if (name.length > MAX_EVENT_NAME_LENGTH) {
27
+ return `event name exceeds ${MAX_EVENT_NAME_LENGTH} character limit: got ${name.length}`;
28
+ }
29
+ if (!EVENT_NAME_REGEX.test(name)) {
30
+ return 'event name must contain only alphanumeric characters, underscores, and dollar signs';
31
+ }
32
+ return null;
33
+ }
34
+ /**
35
+ * Validates event properties.
36
+ *
37
+ * @param properties - The properties object to validate
38
+ * @returns Error message if invalid, null if valid
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * validateProperties({ key: 'value' }); // null
43
+ * ```
44
+ */
45
+ function validateProperties(properties) {
46
+ const keys = Object.keys(properties);
47
+ if (keys.length > MAX_PROPERTIES_COUNT) {
48
+ return `too many properties: got ${keys.length}, max ${MAX_PROPERTIES_COUNT}`;
49
+ }
50
+ for (const key of keys) {
51
+ if (key.length > MAX_PROPERTY_KEY_LENGTH) {
52
+ return `property key "${key}" exceeds ${MAX_PROPERTY_KEY_LENGTH} character limit`;
53
+ }
54
+ const serialized = JSON.stringify(properties[key]);
55
+ if (serialized.length > MAX_PROPERTY_VALUE_SIZE) {
56
+ return `property value for key "${key}" exceeds ${MAX_PROPERTY_VALUE_SIZE} byte limit: got ${serialized.length}`;
57
+ }
58
+ }
59
+ return null;
60
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@bananalytics/react-native",
3
+ "version": "0.1.0",
4
+ "description": "Self-hosted, privacy-first analytics SDK for React Native and Expo apps. Funnels, retention, sessions, geography — your server, your data.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "keywords": [
13
+ "analytics",
14
+ "react-native",
15
+ "expo",
16
+ "self-hosted",
17
+ "privacy",
18
+ "tracking",
19
+ "events",
20
+ "funnels",
21
+ "retention",
22
+ "mixpanel-alternative",
23
+ "amplitude-alternative",
24
+ "posthog-alternative"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "test": "jest",
29
+ "lint": "tsc --noEmit",
30
+ "prepublishOnly": "npm run lint && npm run test && npm run build"
31
+ },
32
+ "peerDependencies": {
33
+ "react": ">=16.8.0",
34
+ "react-native": ">=0.60.0",
35
+ "@react-native-async-storage/async-storage": ">=1.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/jest": "^29.5.12",
39
+ "@types/react": "^18.2.0",
40
+ "@types/uuid": "^9.0.8",
41
+ "jest": "^29.7.0",
42
+ "react": "^18.2.0",
43
+ "react-native": "^0.73.0",
44
+ "ts-jest": "^29.1.2",
45
+ "typescript": "^5.4.0"
46
+ },
47
+ "dependencies": {
48
+ "uuid": "^9.0.1"
49
+ },
50
+ "license": "MIT",
51
+ "author": "Bananalytics",
52
+ "homepage": "https://bananalytics.xyz",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/TableTennisCoder/bananalytics.git",
56
+ "directory": "packages/react-native"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/TableTennisCoder/bananalytics/issues"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }