@recapi/client 0.6.2 → 0.6.3
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/README.md +28 -5
- package/index.d.ts +5 -1
- package/package.json +1 -1
- package/src/client.js +17 -8
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ are, gzipped, and browsers cache them for a year:
|
|
|
35
35
|
|
|
36
36
|
```html
|
|
37
37
|
<script type="module">
|
|
38
|
-
import { createClient } from 'https://api.example.com/sdk/0.6.
|
|
38
|
+
import { createClient } from 'https://api.example.com/sdk/0.6.3/index.js';
|
|
39
39
|
|
|
40
40
|
const recapi = createClient({ apiKey: 'rk_pub_…', baseUrl: 'https://api.example.com' });
|
|
41
41
|
recapi.track('item-42', 'view'); // and the calls of the quick start below
|
|
@@ -327,6 +327,18 @@ repository root on any machine with Docker and python3:
|
|
|
327
327
|
./scripts/e2e_woocommerce.sh
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
+
The file's `Version:` header is that version, so a plugin fix an advisory has
|
|
331
|
+
to name needs a new SDK version (a change to the file alone doesn't move it).
|
|
332
|
+
Its `Update URI:` header, on your API's host, tells WordPress (5.8 or later;
|
|
333
|
+
the plugin needs 6.3) that its updates don't come from the WordPress.org
|
|
334
|
+
directory, so a same-named plugin listed there isn't offered as an update. The
|
|
335
|
+
last part is WordPress.org's documented behaviour, on its side of the update
|
|
336
|
+
check: the end-to-end test shows WordPress reading the header, sending it with
|
|
337
|
+
the check, asking the host's `update_plugins_{hostname}` filter and offering
|
|
338
|
+
nothing, and can't show what WordPress.org would do with a same-named listing.
|
|
339
|
+
Nothing answers that filter yet, so a shop gets no update notice. A snippet
|
|
340
|
+
install has neither header's effect: both are comments there.
|
|
341
|
+
|
|
330
342
|
### Magento
|
|
331
343
|
|
|
332
344
|
The Magento 2 module ([`sdk/php/magento`](../php/magento/README.md)) prints
|
|
@@ -493,7 +505,7 @@ for (const item of items) {
|
|
|
493
505
|
| `historyConfig` | spec defaults | `{ halfLifeSeconds, topK, recentSize }`; must match the server |
|
|
494
506
|
| `consent` | `true` | storage consent, see [Consent](#consent-and-eprivacy) |
|
|
495
507
|
| `respectDoNotTrack` | `true` | when DNT or Global Privacy Control is on: store nothing, send no events, see [Do Not Track](#do-not-track) |
|
|
496
|
-
| `onError` |
|
|
508
|
+
| `onError` | `console.warn` for lost events only | gets errors from background work (flushes, storage, rejected events) and those `recommend()` fell back from |
|
|
497
509
|
| `now`, `fetch`, `sleep` | `Date.now`, global `fetch`, `setTimeout` | for tests |
|
|
498
510
|
|
|
499
511
|
### Client methods
|
|
@@ -536,7 +548,8 @@ for (const item of items) {
|
|
|
536
548
|
were tracked with.
|
|
537
549
|
- **`setConsent(boolean)`**: grants or withdraws storage consent.
|
|
538
550
|
- **`flush()`**: sends queued events now. Never rejects. Errors go to
|
|
539
|
-
`onError
|
|
551
|
+
`onError` ([without one](#event-delivery): lost events to `console.warn`), and
|
|
552
|
+
batches that can still succeed stay queued.
|
|
540
553
|
- **`getHistory()`** returns `{ history, recent, rejected }` exactly as the
|
|
541
554
|
next request would send it (`rejected` goes into `exclude`). Use it for
|
|
542
555
|
debugging.
|
|
@@ -654,6 +667,13 @@ event quota or spend cap is reached, events are lost until it resets or
|
|
|
654
667
|
the plan changes. A 202 with some `rejected` events keeps the accepted
|
|
655
668
|
ones, and the rejected ones are reported as `event_rejected`.
|
|
656
669
|
|
|
670
|
+
Without an `onError`, events lost for good (a batch refused as above,
|
|
671
|
+
`event_rejected`, `queue_overflow`) print one `console.warn` line per error
|
|
672
|
+
code per client, so a wrong key shows in the console without flooding a
|
|
673
|
+
visitor's. Failures that can still succeed, storage errors and the errors
|
|
674
|
+
`recommend()` falls back from print nothing. With an `onError`, nothing is
|
|
675
|
+
printed.
|
|
676
|
+
|
|
657
677
|
When the page is hidden (`visibilitychange` → `hidden`, or `pagehide`),
|
|
658
678
|
the SDK sends the queue at once using `fetch` with `keepalive: true`.
|
|
659
679
|
It does not use `navigator.sendBeacon`, because a beacon cannot send the
|
|
@@ -794,14 +814,17 @@ key on a format change makes old data invisible.
|
|
|
794
814
|
## Size budget
|
|
795
815
|
|
|
796
816
|
Budget: **6 KB gzip** after minification. The sources ship unminified with
|
|
797
|
-
full JSDoc:
|
|
817
|
+
full JSDoc: 58 KB raw in five files, 20 KB as the API serves them (gzip -9),
|
|
798
818
|
and the browser caches them for a year. With comments stripped and no
|
|
799
|
-
minification they are
|
|
819
|
+
minification they are 8.5 KB gzip. A bundler's minifier should bring
|
|
800
820
|
that under the budget, but this has not been measured: the repo has no
|
|
801
821
|
minifier, to keep zero dev dependencies.
|
|
802
822
|
|
|
803
823
|
## Changes
|
|
804
824
|
|
|
825
|
+
- 2026-11-04: version 0.6.3. Without an `onError`, events lost for good (a
|
|
826
|
+
batch refused for good, `event_rejected`, `queue_overflow`) print one
|
|
827
|
+
`console.warn` line per error code per client ([Event delivery](#event-delivery)).
|
|
805
828
|
- 2026-11-03: version 0.6.2. `popular()` and the fallback list of
|
|
806
829
|
`recommend()` no longer throw on React Native below 0.80, whose
|
|
807
830
|
`URLSearchParams.set` is not implemented. Query strings are unchanged
|
package/index.d.ts
CHANGED
|
@@ -83,7 +83,11 @@ export interface ClientOptions {
|
|
|
83
83
|
* nothing, send no events and no visitor ids; recommendations still work.
|
|
84
84
|
*/
|
|
85
85
|
respectDoNotTrack?: boolean;
|
|
86
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Receives errors from background work (flushes, storage) and those
|
|
88
|
+
* `recommend()` fell back from. Without it, events lost for good print one
|
|
89
|
+
* `console.warn` line per error code.
|
|
90
|
+
*/
|
|
87
91
|
onError?: (error: unknown) => void;
|
|
88
92
|
/** Clock in unix ms. */
|
|
89
93
|
now?: () => number;
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -84,7 +84,8 @@ const PUBLIC_KEY_PREFIX = 'rk_pub_';
|
|
|
84
84
|
* Track or Global Privacy Control (default `true`): store nothing, send no
|
|
85
85
|
* events and no visitor ids; recommendations are still asked for.
|
|
86
86
|
* @property {(error: unknown) => void} [onError] Receives background errors,
|
|
87
|
-
* and the errors `recommend()` fell back from.
|
|
87
|
+
* and the errors `recommend()` fell back from. Without it, events lost for
|
|
88
|
+
* good print one `console.warn` line per error code.
|
|
88
89
|
* @property {() => number} [now] Clock in unix ms.
|
|
89
90
|
* @property {typeof fetch} [fetch]
|
|
90
91
|
* @property {(ms: number) => Promise<void>} [sleep] Backoff sleep (tests).
|
|
@@ -115,9 +116,16 @@ export function createClient(options) {
|
|
|
115
116
|
const maxBatch = Math.min(Math.max(1, options.maxBatch ?? 100), MAX_EVENTS_PER_REQUEST);
|
|
116
117
|
const maxQueue = Math.max(MAX_EVENTS_PER_REQUEST, maxBatch);
|
|
117
118
|
const config = resolveConfig(options.historyConfig);
|
|
118
|
-
const
|
|
119
|
+
const warned = new Set();
|
|
120
|
+
// `lost`: events are gone for good. Without an onError nothing else would
|
|
121
|
+
// say so; once per code, or a visitor's console fills up.
|
|
122
|
+
const report = (error, lost) => {
|
|
119
123
|
try {
|
|
120
|
-
options.onError
|
|
124
|
+
if (options.onError) options.onError(error);
|
|
125
|
+
else if (lost && !warned.has(error.code)) {
|
|
126
|
+
warned.add(error.code);
|
|
127
|
+
globalThis.console?.warn(`recapi: events lost: ${error.code}: ${error.message}`);
|
|
128
|
+
}
|
|
121
129
|
} catch {
|
|
122
130
|
// A faulty error handler must not break tracking.
|
|
123
131
|
}
|
|
@@ -242,7 +250,7 @@ export function createClient(options) {
|
|
|
242
250
|
|
|
243
251
|
function dropOverflow() {
|
|
244
252
|
const dropped = queue.splice(0, queue.length - maxQueue).length;
|
|
245
|
-
report(new RecapiError('queue_overflow', `dropped ${dropped} oldest events`));
|
|
253
|
+
report(new RecapiError('queue_overflow', `dropped ${dropped} oldest events`), true);
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
function schedule() {
|
|
@@ -266,13 +274,13 @@ export function createClient(options) {
|
|
|
266
274
|
report(new RecapiError('event_rejected', `${response.rejected.length} event(s) rejected`, {
|
|
267
275
|
status: 202,
|
|
268
276
|
details: response.rejected.map((r) => ({ ...r, event: events[r.index] }))
|
|
269
|
-
}));
|
|
277
|
+
}), true);
|
|
270
278
|
}
|
|
271
279
|
}
|
|
272
280
|
|
|
273
281
|
/** Puts a failed batch back at the head of the queue if it may succeed later. */
|
|
274
282
|
function handleSendError(error, events) {
|
|
275
|
-
report(error);
|
|
283
|
+
report(error, !error.retryable);
|
|
276
284
|
if (error instanceof RecapiError && error.retryable) {
|
|
277
285
|
queue.unshift(...events);
|
|
278
286
|
if (queue.length > maxQueue) dropOverflow();
|
|
@@ -314,8 +322,9 @@ export function createClient(options) {
|
|
|
314
322
|
}
|
|
315
323
|
|
|
316
324
|
/**
|
|
317
|
-
* Sends all queued events. Never rejects: failures go to `onError
|
|
318
|
-
*
|
|
325
|
+
* Sends all queued events. Never rejects: failures go to `onError` (without
|
|
326
|
+
* one, events lost for good to `console.warn`), and retryable batches stay
|
|
327
|
+
* queued for the next automatic flush.
|
|
319
328
|
*
|
|
320
329
|
* @returns {Promise<void>}
|
|
321
330
|
*/
|