@affiliateo/web 4.6.0 → 4.7.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.
package/dist/provider.js CHANGED
@@ -171,6 +171,23 @@ export function getAttribution() {
171
171
  // mount (host has the call sites) but we can shout in dev so the
172
172
  // mistake is visible.
173
173
  let providerMountCount = 0;
174
+ /**
175
+ * True only when we can SEE that this is a production build.
176
+ *
177
+ * Every bundler (webpack, Vite, Next, Rollup) replaces `process.env.NODE_ENV`
178
+ * with a literal before shipping, which is why reading it works for almost
179
+ * everyone. Someone importing the ESM directly has no `process` at all, and
180
+ * touching it there throws a ReferenceError rather than reading undefined.
181
+ *
182
+ * UNKNOWN COUNTS AS NOT-PRODUCTION, deliberately. The only caller is the
183
+ * double-mount warning, and a double mount double-fires every pageview — which
184
+ * corrupts the merchant's own numbers and the affiliate attribution built on
185
+ * them. One stray console line on an unbundled production page is a smaller
186
+ * cost than silently letting that run.
187
+ */
188
+ function isProductionBuild() {
189
+ return typeof process !== 'undefined' && process?.env?.NODE_ENV === 'production';
190
+ }
174
191
  export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, children, }) {
175
192
  // Normalize autoCapturePageviews. Three shapes:
176
193
  // - undefined / true / object → auto-capture ON (with object overrides)
@@ -209,10 +226,8 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
209
226
  const log = useCallback((msg, data) => {
210
227
  if (!debug)
211
228
  return;
212
- // eslint-disable-next-line no-console
213
229
  if (data !== undefined)
214
230
  console.log('[Affiliateo] ' + msg, data);
215
- // eslint-disable-next-line no-console
216
231
  else
217
232
  console.log('[Affiliateo] ' + msg);
218
233
  }, [debug]);
@@ -265,6 +280,18 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
265
280
  visitor_id: vid,
266
281
  session_id: getSessionId(),
267
282
  event_name: name,
283
+ // Page context, same three fields the pageview sender carries.
284
+ // A goal event used to go out without them, and the server's
285
+ // `path || hostname || '[Web]'` fallback then overwrote the
286
+ // visitor's real current_page with the literal string '[Web]' —
287
+ // which both broke the tracking-page popover and made the visitor
288
+ // invisible to any funnel step matching on a page path. The server
289
+ // no longer clobbers on missing values, but sending the real path
290
+ // is what actually keeps current_page correct as a visitor fires
291
+ // goals while navigating a single-page app.
292
+ url: window.location.href,
293
+ path: window.location.pathname,
294
+ hostname: window.location.hostname,
268
295
  };
269
296
  if (metadata && typeof metadata === 'object')
270
297
  payload.metadata = metadata;
@@ -443,7 +470,6 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
443
470
  // Neither appId nor the legacy campaignId alias was passed — nothing to
444
471
  // attribute events to, so stay dormant instead of firing junk.
445
472
  if (!appIdRef.current) {
446
- // eslint-disable-next-line no-console
447
473
  console.error('[Affiliateo] Missing appId — pass your app ID to <AffiliateoProvider appId="...">.');
448
474
  return;
449
475
  }
@@ -451,8 +477,7 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
451
477
  // ever mounted in the page lifetime. Dev-only; production swallows
452
478
  // the warning to avoid noise in real merchant consoles.
453
479
  providerMountCount += 1;
454
- if (providerMountCount > 1 && process.env.NODE_ENV !== 'production') {
455
- // eslint-disable-next-line no-console
480
+ if (providerMountCount > 1 && !isProductionBuild()) {
456
481
  console.warn('[Affiliateo] AffiliateoProvider mounted more than once. Mount it exactly once near the root of your app, or pageviews will double-fire.');
457
482
  }
458
483
  // Client-side bot detection. If the current browser context looks
@@ -952,6 +977,14 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
952
977
  queueRef.current = null;
953
978
  providerMountCount = Math.max(0, providerMountCount - 1);
954
979
  };
980
+ // flushInterval, maxQueueSize, allowFileProtocol and the autoCfg.* fields
981
+ // are BOOT configuration, read once when the queue and listeners are
982
+ // constructed. Listing them would tear the whole pipeline down and rebuild
983
+ // it whenever a host app re-renders with a new inline object, and
984
+ // shutdown() drops the in-memory queue — so honoring the rule here would
985
+ // cost real events. A host that needs different settings should remount
986
+ // the provider.
987
+ // eslint-disable-next-line react-hooks/exhaustive-deps
955
988
  }, [apiBase, allowedHostnames, allowLocalhost, allowIframe, log, track, identify, trackPageview, reset, optOut, optIn, flush]);
956
989
  return (_jsx(AffiliateoContext.Provider, { value: state, children: children }));
957
990
  }
package/dist/queue.js CHANGED
@@ -120,10 +120,27 @@ export class EventQueue {
120
120
  enqueue(endpoint, payload) {
121
121
  if (this.shuttingDown)
122
122
  return;
123
+ const id = uuid();
123
124
  this.queue.push({
124
- id: uuid(),
125
+ id,
125
126
  endpoint,
126
- payload,
127
+ // Delivery metadata is baked into the PERSISTED payload rather than
128
+ // added at flush time, so every retry — and every recovery after a
129
+ // page reload — sends a byte-identical body:
130
+ //
131
+ // event_id makes the server insert idempotent. Without it, a 5xx
132
+ // raised after the row was already written, or a network timeout
133
+ // on a request the server actually processed, lands the same
134
+ // event twice: flush() only removes an entry on res.ok, so
135
+ // anything else bumps retries and sends it again (MAX_RETRIES=3).
136
+ // The server dedups on this via a unique index.
137
+ //
138
+ // client_ts is when the event HAPPENED. The server used to stamp
139
+ // arrival time, but the 5s flush cadence, the size-threshold
140
+ // burst flush, and retries all reorder delivery freely — so
141
+ // arrival order is not user order. Stamping at enqueue is the
142
+ // only point that still knows the real sequence.
143
+ payload: { ...payload, event_id: id, client_ts: Date.now() },
127
144
  retries: 0,
128
145
  });
129
146
  // FIFO drop when over cap. Network outage edge case: if we're way
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@affiliateo/web",
3
- "version": "4.6.0",
4
- "description": "Affiliateo web SDK — affiliate attribution and tracking for web apps",
3
+ "version": "4.7.0",
4
+ "description": "Affiliateo web SDK \u2014 affiliate attribution and tracking for web apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "tsc",
17
- "prepublishOnly": "npm run build"
17
+ "prepack": "npm run build"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "react": ">=18.0.0"
@@ -28,7 +28,17 @@
28
28
  "@types/react": "^19.0.0",
29
29
  "typescript": "^5.0.0"
30
30
  },
31
- "keywords": ["affiliateo", "affiliate", "attribution", "stripe", "web", "session", "tracking"],
31
+ "keywords": [
32
+ "affiliateo",
33
+ "affiliate",
34
+ "attribution",
35
+ "stripe",
36
+ "web",
37
+ "session",
38
+ "tracking"
39
+ ],
32
40
  "license": "MIT",
33
- "files": ["dist"]
41
+ "files": [
42
+ "dist"
43
+ ]
34
44
  }