@cockpitify/js 0.1.0 → 0.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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Cockpitify
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cockpitify
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,43 +1,43 @@
1
- # @cockpitify/js
2
-
3
- Usage analytics for web apps: page views, events and sessions, sent to the `ops` layer that
4
- [Cockpitify](https://cockpitify.app) installs in your own Supabase project. You see them in the Cockpitify app.
5
-
6
- ```sh
7
- npm install @cockpitify/js
8
- ```
9
-
10
- ```ts
11
- import { Ops } from '@cockpitify/js';
12
- import { supabase } from './supabase';
13
-
14
- Ops.init(supabase); // page views are recorded from here on
15
-
16
- // When a feature is used:
17
- Ops.track('report_exported', { rows: 120 });
18
- ```
19
-
20
- Page views come from the History API. Id-like path segments become `[id]`, so `/orders/8412` is
21
- counted as `/orders/[id]`.
22
-
23
- ## Not on Supabase Auth
24
-
25
- Send batches to your own endpoint, which verifies the user and passes the batch on to `ops.ingest_batch`:
26
-
27
- ```ts
28
- import { Ops, httpTransport } from '@cockpitify/js';
29
-
30
- Ops.init(httpTransport({ endpoint: 'https://api.example.com/ops', getToken: () => auth.currentUser?.getIdToken() }));
31
- ```
32
-
33
- ## API
34
-
35
- | | |
36
- |---|---|
37
- | `Ops.init(target, options?)` | `target`: a supabase-js client, an `httpTransport(...)`, or full options. Options: `appVersion`, `pageViews` (default `true`), `screenName(pathname)`, `debug`. Safe to call again. |
38
- | `Ops.track(name, props?)` | An event. Props are strings, numbers or booleans. |
39
- | `Ops.screen(name)` | A screen view, when you name screens yourself. |
40
- | `Ops.flush()` | Sends what is queued now. |
41
- | `Ops.optOut()` / `Ops.optIn()` | Stops or resumes recording on this device. |
42
-
43
- Recording never throws into your code. Events are queued, sent in batches and retried when offline. A new session starts after 30 minutes away.
1
+ # @cockpitify/js
2
+
3
+ Usage analytics for web apps: page views, events and sessions, sent to the `ops` layer that
4
+ [Cockpitify](https://cockpitify.app) installs in your own Supabase project. You see them in the Cockpitify app.
5
+
6
+ ```sh
7
+ npm install @cockpitify/js
8
+ ```
9
+
10
+ ```ts
11
+ import { Ops } from '@cockpitify/js';
12
+ import { supabase } from './supabase';
13
+
14
+ Ops.init(supabase); // page views are recorded from here on
15
+
16
+ // When a feature is used:
17
+ Ops.track('report_exported', { rows: 120 });
18
+ ```
19
+
20
+ Page views come from the History API. Id-like path segments become `[id]`, so `/orders/8412` is
21
+ counted as `/orders/[id]`.
22
+
23
+ ## Not on Supabase Auth
24
+
25
+ Send batches to your own endpoint, which verifies the user and passes the batch on to `ops.ingest_batch`:
26
+
27
+ ```ts
28
+ import { Ops, httpTransport } from '@cockpitify/js';
29
+
30
+ Ops.init(httpTransport({ endpoint: 'https://api.example.com/ops', getToken: () => auth.currentUser?.getIdToken() }));
31
+ ```
32
+
33
+ ## API
34
+
35
+ | | |
36
+ |---|---|
37
+ | `Ops.init(target, options?)` | `target`: a supabase-js client, an `httpTransport(...)`, or full options. Options: `appVersion`, `pageViews` (default `true`), `screenName(pathname)`, `debug`. Safe to call again. |
38
+ | `Ops.track(name, props?)` | An event. Props are strings, numbers or booleans. |
39
+ | `Ops.screen(name)` | A screen view, when you name screens yourself. |
40
+ | `Ops.flush()` | Sends what is queued now. |
41
+ | `Ops.optOut()` / `Ops.optIn()` | Stops or resumes recording on this device. |
42
+
43
+ Recording never throws into your code. Events are queued, sent in batches and retried when offline. A new session starts after 30 minutes away.
package/dist/core.d.ts CHANGED
@@ -24,6 +24,11 @@ export declare class OpsClient {
24
24
  constructor(opts: OpsOptions);
25
25
  /** A feature was used. */
26
26
  track(name: string, props?: Record<string, unknown>): void;
27
+ /**
28
+ * Revenue (an ad impression's earnings, …): like `track`, but never sampled out, since a margin
29
+ * built from a sample would be wrong. The user's opt-out still holds.
30
+ */
31
+ trackRevenue(name: string, props?: Record<string, unknown>): void;
27
32
  /** The app moved to a screen (route pattern, e.g. "/quiz/[id]"). The previous one is closed and timed. */
28
33
  setScreen(name: string): void;
29
34
  /** Sends what is queued now (also called on background). Never throws. */
@@ -37,6 +42,7 @@ export declare class OpsClient {
37
42
  private boot;
38
43
  private startTimer;
39
44
  private applyConfig;
45
+ /** `essential`: revenue, kept whatever the sample rate. */
40
46
  private recording;
41
47
  /** Starts a session, or a new one after 30 minutes away (closing the old one at its last activity). */
42
48
  private ensureSession;
package/dist/core.js CHANGED
@@ -98,6 +98,16 @@ export class OpsClient {
98
98
  this.push({ type: 'event', name: name.trim().slice(0, 100), t: this.clock.now(), ...(clean ? { props: clean } : {}) });
99
99
  });
100
100
  }
101
+ /**
102
+ * Revenue (an ad impression's earnings, …): like `track`, but never sampled out, since a margin
103
+ * built from a sample would be wrong. The user's opt-out still holds.
104
+ */
105
+ trackRevenue(name, props) {
106
+ void this.ready.then(() => {
107
+ const clean = cleanProps(props);
108
+ this.push({ type: 'event', name: name.trim().slice(0, 100), t: this.clock.now(), ...(clean ? { props: clean } : {}) }, undefined, true);
109
+ });
110
+ }
101
111
  /** The app moved to a screen (route pattern, e.g. "/quiz/[id]"). The previous one is closed and timed. */
102
112
  setScreen(name) {
103
113
  void this.ready.then(() => {
@@ -217,8 +227,9 @@ export class OpsClient {
217
227
  if (changedTimer)
218
228
  this.startTimer();
219
229
  }
220
- recording() {
221
- return !this.optedOut && this.config.enabled && deviceFraction(this.deviceId) < this.config.sampleRate;
230
+ /** `essential`: revenue, kept whatever the sample rate. */
231
+ recording(essential = false) {
232
+ return !this.optedOut && this.config.enabled && (essential || deviceFraction(this.deviceId) < this.config.sampleRate);
222
233
  }
223
234
  /** Starts a session, or a new one after 30 minutes away (closing the old one at its last activity). */
224
235
  ensureSession(now) {
@@ -261,8 +272,8 @@ export class OpsClient {
261
272
  if (this.screen)
262
273
  this.screen.enteredAt = now;
263
274
  }
264
- push(e, sessionId) {
265
- if (!this.recording() || !e.name)
275
+ push(e, sessionId, essential = false) {
276
+ if (!this.recording(essential) || !e.name)
266
277
  return;
267
278
  if (this.foreground && this.session && e.type !== 'session_end')
268
279
  this.session.lastActiveAt = Math.max(this.session.lastActiveAt, this.clock.now());
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { httpTransport, supabaseTransport, isSupabaseClient, type HttpTransportO
5
5
  export { createSingleton, resolveOptions, type InitTarget, type OpsApi } from './singleton.js';
6
6
  export { routePattern, watchHistory } from './web.js';
7
7
  export type * from './types.js';
8
- export declare const VERSION = "0.1.0";
8
+ export declare const VERSION = "0.1.1";
9
9
  export interface WebInitOptions {
10
10
  /** Your app's version, shown in the console's version breakdown. */
11
11
  appVersion?: string;
@@ -19,6 +19,7 @@ export declare const Ops: {
19
19
  /** A Supabase client, an `httpTransport(...)`, or full options. Safe to call again (re-initialises). */
20
20
  init(target: InitTarget, options?: WebInitOptions): OpsClient;
21
21
  track(name: string, props?: Record<string, unknown>): void;
22
+ trackRevenue(name: string, props?: Record<string, unknown>): void;
22
23
  screen(name: string): void;
23
24
  flush(): Promise<void>;
24
25
  optOut(): void;
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ export { OpsClient, cleanProps, randomUuid, SESSION_GAP_MS } from "./core.js";
10
10
  export { httpTransport, supabaseTransport, isSupabaseClient } from "./adapters.js";
11
11
  export { createSingleton, resolveOptions } from "./singleton.js";
12
12
  export { routePattern, watchHistory } from "./web.js";
13
- export const VERSION = '0.1.0';
13
+ export const VERSION = '0.1.1';
14
14
  const singleton = createSingleton();
15
15
  let stopHistory = null;
16
16
  export const Ops = {
@@ -6,6 +6,8 @@ export type InitTarget = SupabaseLike | Transport | OpsOptions;
6
6
  export declare function resolveOptions(target: InitTarget, defaults: Omit<OpsOptions, 'transport'>): OpsOptions;
7
7
  export interface OpsApi {
8
8
  track(name: string, props?: Record<string, unknown>): void;
9
+ /** Revenue events: never sampled out (see OpsClient.trackRevenue). */
10
+ trackRevenue(name: string, props?: Record<string, unknown>): void;
9
11
  screen(name: string): void;
10
12
  flush(): Promise<void>;
11
13
  optOut(): void;
package/dist/singleton.js CHANGED
@@ -27,6 +27,7 @@ export function createSingleton() {
27
27
  fn(client);
28
28
  },
29
29
  track: (name, props) => run((c) => c.track(name, props)),
30
+ trackRevenue: (name, props) => run((c) => c.trackRevenue(name, props)),
30
31
  screen: (name) => run((c) => c.setScreen(name)),
31
32
  flush: () => current?.flush() ?? Promise.resolve(),
32
33
  optOut: () => run((c) => c.optOut()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cockpitify/js",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Cockpitify usage analytics for web apps: screen views, events and sessions.",
5
5
  "license": "MIT",
6
6
  "author": "Cockpitify",