pulse-updates 1.0.21 → 1.1.1

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.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * One call to wire an app to Pulse.
3
+ *
4
+ * The three halves — updates, config, events — each have their own setup, and each
5
+ * one is a place to make a mistake that shows up as an empty chart weeks later: a
6
+ * config url that points at one app and a track url at another, an install with no
7
+ * stable id (so every launch looks like a new device and every arm is re-dealt), a
8
+ * context that forgets the app version (so an experiment cannot be aimed at a build).
9
+ * They are the same three facts every time, so they are asked for once.
10
+ *
11
+ * Everything it derives, it derives from the platform and only when the caller has
12
+ * not said otherwise:
13
+ *
14
+ * - the urls, from the base url and the slug, which is what stops the two halves
15
+ * from ever addressing different apps;
16
+ * - platform and OS version, from React Native;
17
+ * - the device id, minted and persisted on first launch — an app that has a stable
18
+ * identifier of its own (the vendor id, an installation id) should pass it, and an
19
+ * app that has none should not have to invent one to be measurable.
20
+ *
21
+ * Deliberately not automatic: the app version. Nothing here can read the store build
22
+ * reliably on both platforms, and a version guessed wrong aims a rollout at the wrong
23
+ * installs — a targeting bug that looks like a rollout that "did not work".
24
+ */
25
+
26
+ import { configureConfig, fetchConfig, startConfigAutoRefresh } from './config';
27
+ import { configureTracking } from './track';
28
+ const DEVICE_ID_KEY = 'pulse.device-id';
29
+
30
+ /** Wires config and events, and returns the context the two share. */
31
+ export function initPulse(opts) {
32
+ const base = opts.apiUrl.replace(/\/+$/, '');
33
+ const slug = opts.appSlug.trim();
34
+ const configUrl = `${base}/pulse/config/${slug}`;
35
+ const trackUrl = `${base}/pulse/track/${slug}`;
36
+ const deviceId = opts.deviceId?.trim() || resolveDeviceId(opts.storage);
37
+ const platform = detectPlatform();
38
+ const osVersion = detectOsVersion();
39
+ const getContext = () => ({
40
+ platform,
41
+ osVersion,
42
+ appVersion: opts.appVersion,
43
+ deviceId,
44
+ userId: opts.getUserId?.(),
45
+ userAttributes: opts.getUserAttributes?.()
46
+ });
47
+ configureConfig({
48
+ url: configUrl,
49
+ defaults: opts.defaults,
50
+ storage: opts.storage,
51
+ getContext,
52
+ pollIntervalMs: opts.pollIntervalMs,
53
+ onError: opts.onError
54
+ });
55
+ configureTracking({
56
+ url: trackUrl,
57
+ getContext,
58
+ flushIntervalMs: opts.flushIntervalMs,
59
+ onIgnored: opts.onIgnoredEvents,
60
+ onError: opts.onError
61
+ });
62
+
63
+ // The two fields nothing can derive, said out loud when they are missing. Both
64
+ // fail silently and late: without a version, every rule and slice that names one
65
+ // simply does not match this install — which reads as "the rollout did nothing".
66
+ // Without storage the id is new on every launch, so the arm is re-dealt each time
67
+ // and no experiment can mean anything.
68
+ if (!opts.appVersion) {
69
+ opts.onError?.(new Error('Pulse: no appVersion given — version targeting and version slices will not match this install'));
70
+ }
71
+ if (!opts.storage && !opts.deviceId) {
72
+ opts.onError?.(new Error('Pulse: no storage and no deviceId — this install gets a new id every launch, which re-deals its arm'));
73
+ }
74
+
75
+ // Fetch now rather than on the first read: a launch that reads the config before
76
+ // the first response is the launch that runs on defaults, and defaults are the
77
+ // off position of every flag.
78
+ void fetchConfig();
79
+ startConfigAutoRefresh(opts.appState);
80
+ return {
81
+ deviceId,
82
+ configUrl,
83
+ trackUrl
84
+ };
85
+ }
86
+
87
+ /**
88
+ * The install's id: the one it already had, or a new one kept from now on.
89
+ *
90
+ * Without storage this is a new id every launch, which is worse than it looks — the
91
+ * arm is a hash of it, so every launch would be a fresh coin toss and no experiment
92
+ * could mean anything. Said out loud through onError rather than hidden.
93
+ */
94
+ function resolveDeviceId(storage) {
95
+ if (!storage) return randomId();
96
+ try {
97
+ const existing = storage.getString(DEVICE_ID_KEY);
98
+ if (existing) return existing;
99
+ const minted = randomId();
100
+ storage.set(DEVICE_ID_KEY, minted);
101
+ return minted;
102
+ } catch {
103
+ return randomId();
104
+ }
105
+ }
106
+ function randomId() {
107
+ const bytes = new Uint8Array(16);
108
+ const crypto = globalThis.crypto;
109
+ if (crypto?.getRandomValues) {
110
+ crypto.getRandomValues(bytes);
111
+ } else {
112
+ // React Native without a crypto polyfill. Weaker, and only ever used to tell one
113
+ // install from another — never as a secret.
114
+ for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
115
+ }
116
+ bytes[6] = (bytes[6] ?? 0) & 0x0f | 0x40;
117
+ bytes[8] = (bytes[8] ?? 0) & 0x3f | 0x80;
118
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
119
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
120
+ }
121
+
122
+ /** React Native when it is there, and nothing invented when it is not. */
123
+ function detectPlatform() {
124
+ try {
125
+ // Required lazily so the module stays importable from a plain Node script — the
126
+ // CLI and the tests both do that.
127
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
128
+ const rn = require('react-native');
129
+ return rn?.Platform?.OS;
130
+ } catch {
131
+ return undefined;
132
+ }
133
+ }
134
+ function detectOsVersion() {
135
+ try {
136
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
137
+ const rn = require('react-native');
138
+ const version = rn?.Platform?.Version;
139
+ return version === undefined || version === null ? undefined : String(version);
140
+ } catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["configureConfig","fetchConfig","startConfigAutoRefresh","configureTracking","DEVICE_ID_KEY","initPulse","opts","base","apiUrl","replace","slug","appSlug","trim","configUrl","trackUrl","deviceId","resolveDeviceId","storage","platform","detectPlatform","osVersion","detectOsVersion","getContext","appVersion","userId","getUserId","userAttributes","getUserAttributes","url","defaults","pollIntervalMs","onError","flushIntervalMs","onIgnored","onIgnoredEvents","Error","appState","randomId","existing","getString","minted","set","bytes","Uint8Array","crypto","globalThis","getRandomValues","i","length","Math","floor","random","hex","Array","from","b","toString","padStart","join","slice","rn","require","Platform","OS","undefined","version","Version","String"],"sourceRoot":"../../src","sources":["init.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Pulse Track — the app's own events, sent to Pulse.
3
+ *
4
+ * The point of this half: an experiment is only worth running if something can be
5
+ * measured on it, and until now measuring meant owning a warehouse — a ClickHouse,
6
+ * a stream, and somebody to keep both alive. An app that has just adopted Pulse has
7
+ * none of that, so its experiments could report retention and nothing else.
8
+ *
9
+ * The event has to be declared in the registry first. That is deliberate and it is
10
+ * the same rule the server enforces: an event nobody declared could never be named by
11
+ * a metric, so accepting it would be a write into a place nothing reads. The response
12
+ * says which keys were ignored, and `onIgnored` surfaces them — a typo in an event
13
+ * name is otherwise invisible until someone wonders why a chart is empty.
14
+ *
15
+ * Batched on purpose. The endpoint takes up to a hundred events per request and is
16
+ * rate-limited per address, so one request per event would spend the whole allowance
17
+ * of a shared network on a single install. Events are queued, flushed on a timer and
18
+ * whenever the batch fills, and dropped oldest-first if the queue overflows: an event
19
+ * lost is a row missing from an average, and a request that blocks the app is a bug
20
+ * everybody sees.
21
+ */
22
+
23
+ const DEFAULT_FLUSH_MS = 10_000;
24
+ const DEFAULT_MAX_BATCH = 100;
25
+ const DEFAULT_MAX_QUEUE = 500;
26
+ let options = null;
27
+ let queue = [];
28
+ let timer = null;
29
+ let sending = false;
30
+
31
+ /** Turns a config url into the track url; leaves an explicit track url alone. */
32
+ function trackUrl(url) {
33
+ const trimmed = url.replace(/\/+$/, '');
34
+ return trimmed.includes('/pulse/config/') ? trimmed.replace('/pulse/config/', '/pulse/track/') : trimmed;
35
+ }
36
+ export function configureTracking(opts) {
37
+ options = opts;
38
+ startTimer();
39
+ }
40
+
41
+ /**
42
+ * Queue an event. Never throws, never awaits: a call site in a tap handler must not
43
+ * be able to make the tap slow, and an analytics call that can throw is one every
44
+ * caller has to wrap.
45
+ */
46
+ export function track(event, props, time) {
47
+ if (!options || !event) return;
48
+ const flat = {};
49
+ for (const [key, value] of Object.entries(props ?? {})) {
50
+ if (value === null || value === undefined) continue;
51
+ flat[key] = typeof value === 'string' ? value : String(value);
52
+ }
53
+ queue.push({
54
+ event,
55
+ time: (time ?? new Date()).toISOString(),
56
+ props: flat
57
+ });
58
+ const maxQueue = options.maxQueue ?? DEFAULT_MAX_QUEUE;
59
+ if (queue.length > maxQueue) queue = queue.slice(queue.length - maxQueue);
60
+ if (queue.length >= (options.maxBatch ?? DEFAULT_MAX_BATCH)) void flushEvents();
61
+ }
62
+
63
+ /**
64
+ * Send what is queued. Returns how many events the server accepted.
65
+ *
66
+ * Failures put the batch back at the front of the queue rather than dropping it: the
67
+ * usual cause is a network that is about to come back, and the usual moment is right
68
+ * after launch, when the first events of a session are the ones a funnel needs most.
69
+ */
70
+ export async function flushEvents() {
71
+ if (!options || sending || queue.length === 0) return 0;
72
+ const opts = options;
73
+ const ctx = opts.getContext?.() ?? {};
74
+
75
+ // Without a device id the server cannot put the event in an arm, and an empty id
76
+ // would pool every such install into one. Held rather than dropped: the id usually
77
+ // arrives a moment after boot.
78
+ if (!ctx.deviceId) return 0;
79
+ const batch = queue.slice(0, opts.maxBatch ?? DEFAULT_MAX_BATCH);
80
+ queue = queue.slice(batch.length);
81
+ sending = true;
82
+ try {
83
+ const response = await fetch(trackUrl(opts.url), {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json'
87
+ },
88
+ body: JSON.stringify({
89
+ deviceId: ctx.deviceId,
90
+ userId: ctx.userId,
91
+ platform: ctx.platform,
92
+ appVersion: ctx.appVersion,
93
+ events: batch.map(e => ({
94
+ event: e.event,
95
+ time: e.time,
96
+ props: e.props
97
+ }))
98
+ })
99
+ });
100
+ if (!response.ok) {
101
+ // Rate limited or briefly down: keep the events, try on the next tick.
102
+ queue = [...batch, ...queue];
103
+ if (response.status !== 429) opts.onError?.(new Error(`Pulse track failed: ${response.status}`));
104
+ return 0;
105
+ }
106
+ const body = await response.json();
107
+ if (body.ignored && body.ignored.length > 0) opts.onIgnored?.(body.ignored);
108
+ return body.accepted ?? 0;
109
+ } catch (error) {
110
+ queue = [...batch, ...queue];
111
+ opts.onError?.(error);
112
+ return 0;
113
+ } finally {
114
+ sending = false;
115
+ }
116
+ }
117
+
118
+ /** How many events are waiting — for a caller that wants to flush before backgrounding. */
119
+ export function pendingEventCount() {
120
+ return queue.length;
121
+ }
122
+ export function stopTracking() {
123
+ if (timer) {
124
+ clearInterval(timer);
125
+ timer = null;
126
+ }
127
+ options = null;
128
+ queue = [];
129
+ sending = false;
130
+ }
131
+ function startTimer() {
132
+ if (timer) clearInterval(timer);
133
+ const every = options?.flushIntervalMs ?? DEFAULT_FLUSH_MS;
134
+ timer = setInterval(() => void flushEvents(), every);
135
+ // Node keeps the process alive for a pending interval; React Native does not care,
136
+ // and a CLI importing this should still be able to exit.
137
+ timer.unref?.();
138
+ }
139
+ //# sourceMappingURL=track.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["DEFAULT_FLUSH_MS","DEFAULT_MAX_BATCH","DEFAULT_MAX_QUEUE","options","queue","timer","sending","trackUrl","url","trimmed","replace","includes","configureTracking","opts","startTimer","track","event","props","time","flat","key","value","Object","entries","undefined","String","push","Date","toISOString","maxQueue","length","slice","maxBatch","flushEvents","ctx","getContext","deviceId","batch","response","fetch","method","headers","body","JSON","stringify","userId","platform","appVersion","events","map","e","ok","status","onError","Error","json","ignored","onIgnored","accepted","error","pendingEventCount","stopTracking","clearInterval","every","flushIntervalMs","setInterval","unref"],"sourceRoot":"../../src","sources":["track.ts"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -3,4 +3,6 @@ export * from './types';
3
3
  export { usePulseUpdates } from './usePulseUpdates';
4
4
  export { initializeAssetResolver, updateLocalAssets } from './assetResolver';
5
5
  export * from './config';
6
+ export * from './track';
7
+ export * from './init';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * One call to wire an app to Pulse.
3
+ *
4
+ * The three halves — updates, config, events — each have their own setup, and each
5
+ * one is a place to make a mistake that shows up as an empty chart weeks later: a
6
+ * config url that points at one app and a track url at another, an install with no
7
+ * stable id (so every launch looks like a new device and every arm is re-dealt), a
8
+ * context that forgets the app version (so an experiment cannot be aimed at a build).
9
+ * They are the same three facts every time, so they are asked for once.
10
+ *
11
+ * Everything it derives, it derives from the platform and only when the caller has
12
+ * not said otherwise:
13
+ *
14
+ * - the urls, from the base url and the slug, which is what stops the two halves
15
+ * from ever addressing different apps;
16
+ * - platform and OS version, from React Native;
17
+ * - the device id, minted and persisted on first launch — an app that has a stable
18
+ * identifier of its own (the vendor id, an installation id) should pass it, and an
19
+ * app that has none should not have to invent one to be measurable.
20
+ *
21
+ * Deliberately not automatic: the app version. Nothing here can read the store build
22
+ * reliably on both platforms, and a version guessed wrong aims a rollout at the wrong
23
+ * installs — a targeting bug that looks like a rollout that "did not work".
24
+ */
25
+ import { type ConfigStorage, type ConfigValue } from './config';
26
+ export interface InitPulseOptions {
27
+ /** Where Pulse lives, e.g. https://pulse.example.com — no path. */
28
+ apiUrl: string;
29
+ /** The app's slug in Pulse. Both urls are built from it. */
30
+ appSlug: string;
31
+ /** Values in force before the server has ever been reached. */
32
+ defaults?: Record<string, ConfigValue>;
33
+ /**
34
+ * Somewhere to keep the last good config and the device id. Without it both are
35
+ * memory-only: the config falls back to defaults after a restart, and — worse —
36
+ * the install gets a new id every launch, which re-deals its arm every launch.
37
+ */
38
+ storage?: ConfigStorage;
39
+ /** The store build, e.g. "5.2.0". Needed to target a rollout at a version. */
40
+ appVersion?: string;
41
+ /**
42
+ * A stable id for this install. Left out, one is minted and persisted.
43
+ *
44
+ * Worth passing when the app already has one the rest of its analytics uses: the
45
+ * arm is a hash of this value, and a metric joined on a different id is not
46
+ * slightly wrong, it is randomised.
47
+ */
48
+ deviceId?: string;
49
+ /** The signed-in account, read fresh on every request. Only user-level experiments use it. */
50
+ getUserId?: () => string | undefined;
51
+ /** Attributes a rule can target — a plan, a storefront. Keep them few and stable. */
52
+ getUserAttributes?: () => Record<string, string> | undefined;
53
+ /** Foreground poll interval for the config. 0 disables it; launch and resume still fetch. */
54
+ pollIntervalMs?: number;
55
+ /** How often queued events are sent. Default 10s. */
56
+ flushIntervalMs?: number;
57
+ /** App-state hook, so the config refreshes on return from background. */
58
+ appState?: {
59
+ addEventListener: (type: 'change', handler: (state: string) => void) => {
60
+ remove: () => void;
61
+ };
62
+ };
63
+ /** Event keys the server did not recognise — a typo, or an event nobody declared. */
64
+ onIgnoredEvents?: (events: string[]) => void;
65
+ onError?: (error: unknown) => void;
66
+ }
67
+ /** Wires config and events, and returns the context the two share. */
68
+ export declare function initPulse(opts: InitPulseOptions): {
69
+ deviceId: string;
70
+ configUrl: string;
71
+ trackUrl: string;
72
+ };
73
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,UAAU,CAAC;AAGlB,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IAEf,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAEhB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAEvC;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,8FAA8F;IAC9F,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAErC,qFAAqF;IACrF,iBAAiB,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAE7D,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,yEAAyE;IACzE,QAAQ,CAAC,EAAE;QAAE,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK;YAAE,MAAM,EAAE,MAAM,IAAI,CAAA;SAAE,CAAA;KAAE,CAAC;IAE9G,qFAAqF;IACrF,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAE7C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAID,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAyD3G"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pulse Track — the app's own events, sent to Pulse.
3
+ *
4
+ * The point of this half: an experiment is only worth running if something can be
5
+ * measured on it, and until now measuring meant owning a warehouse — a ClickHouse,
6
+ * a stream, and somebody to keep both alive. An app that has just adopted Pulse has
7
+ * none of that, so its experiments could report retention and nothing else.
8
+ *
9
+ * The event has to be declared in the registry first. That is deliberate and it is
10
+ * the same rule the server enforces: an event nobody declared could never be named by
11
+ * a metric, so accepting it would be a write into a place nothing reads. The response
12
+ * says which keys were ignored, and `onIgnored` surfaces them — a typo in an event
13
+ * name is otherwise invisible until someone wonders why a chart is empty.
14
+ *
15
+ * Batched on purpose. The endpoint takes up to a hundred events per request and is
16
+ * rate-limited per address, so one request per event would spend the whole allowance
17
+ * of a shared network on a single install. Events are queued, flushed on a timer and
18
+ * whenever the batch fills, and dropped oldest-first if the queue overflows: an event
19
+ * lost is a row missing from an average, and a request that blocks the app is a bug
20
+ * everybody sees.
21
+ */
22
+ import type { ConfigContext } from './config';
23
+ export interface TrackOptions {
24
+ /**
25
+ * The config url this app already uses (…/pulse/config/{slug}) or the track url
26
+ * itself. Accepting the first is not a convenience: it means the two halves cannot
27
+ * end up pointing at different apps, which would put an app's events in another
28
+ * app's registry and read as an empty metric on both sides.
29
+ */
30
+ url: string;
31
+ /** The same context the config uses — identity travels with the batch, once. */
32
+ getContext?: () => ConfigContext;
33
+ /** How often a non-empty queue is sent. Default 10s. */
34
+ flushIntervalMs?: number;
35
+ /** Events per request. The server refuses more than 100. */
36
+ maxBatch?: number;
37
+ /** Events held before the oldest are dropped. Default 500. */
38
+ maxQueue?: number;
39
+ /** Called with the event keys the server did not recognise. */
40
+ onIgnored?: (events: string[]) => void;
41
+ onError?: (error: unknown) => void;
42
+ }
43
+ export interface TrackedEventInput {
44
+ event: string;
45
+ props?: Record<string, string | number | boolean | null | undefined>;
46
+ time?: Date;
47
+ }
48
+ export declare function configureTracking(opts: TrackOptions): void;
49
+ /**
50
+ * Queue an event. Never throws, never awaits: a call site in a tap handler must not
51
+ * be able to make the tap slow, and an analytics call that can throw is one every
52
+ * caller has to wrap.
53
+ */
54
+ export declare function track(event: string, props?: TrackedEventInput['props'], time?: Date): void;
55
+ /**
56
+ * Send what is queued. Returns how many events the server accepted.
57
+ *
58
+ * Failures put the batch back at the front of the queue rather than dropping it: the
59
+ * usual cause is a network that is about to come back, and the usual moment is right
60
+ * after launch, when the first events of a session are the ones a funnel needs most.
61
+ */
62
+ export declare function flushEvents(): Promise<number>;
63
+ /** How many events are waiting — for a caller that wants to flush before backgrounding. */
64
+ export declare function pendingEventCount(): number;
65
+ export declare function stopTracking(): void;
66
+ //# sourceMappingURL=track.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/track.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,aAAa,CAAC;IAEjC,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAEvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAyBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAG1D;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAe1F;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CA6CnD;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,YAAY,IAAI,IAAI,CAQnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.0.21",
3
+ "version": "1.1.1",
4
4
  "description": "OTA updates for React Native - lightweight alternative to expo-updates",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",