@affiliateo/web 4.9.1 → 4.9.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 CHANGED
@@ -270,6 +270,12 @@ the script tag, `data-chat-color`, `data-chat-icon-color`, `data-chat-send-color
270
270
 
271
271
  ## Changelog
272
272
 
273
+ - **4.9.3:** the events still waiting when a page closes now reach Affiliateo
274
+ on websites with strict privacy settings too (a referrer policy of
275
+ `no-referrer` or `same-origin`, the defaults of Helmet and Django). Nothing
276
+ to change in your code.
277
+ - **4.9.2:** events are sent as plain text, so the browser no longer makes an
278
+ extra check request before each one. Nothing to change in your code.
273
279
  - **4.9.1:** a different user signing in with `identify()` (no `logout()` in
274
280
  between) now forgets the last person's Live Chat thread first, as a sign-out
275
281
  does, and the bubble learns the new user without a reload. Signing in from
package/dist/queue.d.ts CHANGED
@@ -84,15 +84,15 @@ export declare class EventQueue {
84
84
  * burst path + the timer firing simultaneously) only run one
85
85
  * flush at a time. */
86
86
  flush(): Promise<void>;
87
- /** Best-effort sync flush triggered on page unload. Uses sendBeacon
88
- * for each event so it survives the tab closing. No retry - this is
89
- * the last chance.
87
+ /** Best-effort sync flush triggered on page unload: each event goes out
88
+ * in a way that survives the tab closing (sendAtUnload). No retry - this
89
+ * is the last chance.
90
90
  *
91
91
  * Called from the provider's pagehide / visibilitychange=hidden
92
92
  * handlers so any events still queued at unload get one last shot.
93
- * After this call, the in-memory queue is cleared (events that
94
- * sendBeacon couldn't queue are lost - that's the cost of the
95
- * guaranteed-fast unload path). */
93
+ * After this call, the in-memory queue is cleared (events the browser
94
+ * couldn't take are lost - that's the cost of the guaranteed-fast
95
+ * unload path). */
96
96
  flushOnUnload(): void;
97
97
  /** Remove the queued events `predicate` picks (4.8.0). A sign-in uses it
98
98
  * to drop a sign-out still waiting to be delivered: the sign-in replaces
package/dist/queue.js CHANGED
@@ -46,6 +46,36 @@ const MAX_RETRIES = 3;
46
46
  const DEFAULT_MAX_QUEUE_SIZE = 100;
47
47
  const DEFAULT_FLUSH_INTERVAL_MS = 5000;
48
48
  const SIZE_FLUSH_THRESHOLD = 10;
49
+ // Plain text, not application/json: a JSON body makes the browser send an
50
+ // extra OPTIONS request before every event. The server reads the body as
51
+ // text and parses it itself, so nothing else changes.
52
+ const BODY_TYPE = 'text/plain;charset=UTF-8';
53
+ /** Whether a fetch here outlives the page (keepalive; Firefox only since 133). */
54
+ const KEEPALIVE_FETCH = typeof Request === 'function' && 'keepalive' in Request.prototype;
55
+ /**
56
+ * One last send as the page goes away. A fetch in CORS mode with keepalive,
57
+ * not sendBeacon: a plain-text beacon goes out as no-cors, and on a site
58
+ * whose referrer policy is no-referrer or same-origin (Helmet's and Django's
59
+ * defaults) the browser then sends "Origin: null", which the server refuses,
60
+ * since it takes events only from the app's own site. A CORS fetch always
61
+ * carries the real Origin, is still a simple request (no OPTIONS), and
62
+ * keepalive lets it outlive the page the way a beacon does. Browsers without
63
+ * keepalive keep the beacon (4.9.3). Same in t.js (public/t.js, post()).
64
+ */
65
+ function sendAtUnload(endpoint, body) {
66
+ try {
67
+ if (KEEPALIVE_FETCH) {
68
+ fetch(endpoint, { method: 'POST', mode: 'cors', credentials: 'omit', headers: { 'Content-Type': BODY_TYPE }, body, keepalive: true }).catch(() => { });
69
+ return;
70
+ }
71
+ if (typeof navigator !== 'undefined' && navigator.sendBeacon && navigator.sendBeacon(endpoint, new Blob([body], { type: BODY_TYPE })))
72
+ return;
73
+ fetch(endpoint, { method: 'POST', headers: { 'Content-Type': BODY_TYPE }, body, keepalive: true }).catch(() => { });
74
+ }
75
+ catch {
76
+ // The last chance: nothing left to do.
77
+ }
78
+ }
49
79
  function lsGet(key) {
50
80
  try {
51
81
  return localStorage.getItem(key);
@@ -188,7 +218,7 @@ export class EventQueue {
188
218
  try {
189
219
  const res = await fetch(event.endpoint, {
190
220
  method: 'POST',
191
- headers: { 'Content-Type': 'application/json' },
221
+ headers: { 'Content-Type': BODY_TYPE },
192
222
  body: JSON.stringify(event.payload),
193
223
  // keepalive lets the request survive a navigation. paired
194
224
  // with the queue so even if this fetch errors, we'll retry.
@@ -223,43 +253,20 @@ export class EventQueue {
223
253
  this.persist();
224
254
  this.isFlushing = false;
225
255
  }
226
- /** Best-effort sync flush triggered on page unload. Uses sendBeacon
227
- * for each event so it survives the tab closing. No retry - this is
228
- * the last chance.
256
+ /** Best-effort sync flush triggered on page unload: each event goes out
257
+ * in a way that survives the tab closing (sendAtUnload). No retry - this
258
+ * is the last chance.
229
259
  *
230
260
  * Called from the provider's pagehide / visibilitychange=hidden
231
261
  * handlers so any events still queued at unload get one last shot.
232
- * After this call, the in-memory queue is cleared (events that
233
- * sendBeacon couldn't queue are lost - that's the cost of the
234
- * guaranteed-fast unload path). */
262
+ * After this call, the in-memory queue is cleared (events the browser
263
+ * couldn't take are lost - that's the cost of the guaranteed-fast
264
+ * unload path). */
235
265
  flushOnUnload() {
236
266
  if (this.queue.length === 0)
237
267
  return;
238
- if (typeof navigator === 'undefined' || !navigator.sendBeacon) {
239
- // No sendBeacon (very old browser) - try a sync fetch with
240
- // keepalive as a fallback. Won't actually finish, but it's our
241
- // last shot.
242
- for (const event of this.queue) {
243
- try {
244
- fetch(event.endpoint, {
245
- method: 'POST',
246
- headers: { 'Content-Type': 'application/json' },
247
- body: JSON.stringify(event.payload),
248
- keepalive: true,
249
- }).catch(() => { });
250
- }
251
- catch { /* swallow */ }
252
- }
253
- this.queue = [];
254
- this.persist();
255
- return;
256
- }
257
- for (const event of this.queue) {
258
- try {
259
- navigator.sendBeacon(event.endpoint, new Blob([JSON.stringify(event.payload)], { type: 'application/json' }));
260
- }
261
- catch { /* swallow */ }
262
- }
268
+ for (const event of this.queue)
269
+ sendAtUnload(event.endpoint, JSON.stringify(event.payload));
263
270
  this.queue = [];
264
271
  this.persist();
265
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@affiliateo/web",
3
- "version": "4.9.1",
3
+ "version": "4.9.3",
4
4
  "description": "Affiliateo web SDK: affiliate attribution and tracking for web apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",