@affiliateo/web 4.9.0 → 4.9.2

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
@@ -233,7 +233,8 @@ signs in next (or nobody) isn't counted as the person who just left. Their Live
233
233
  Chat goes too: the next person to open the chat starts a new thread.
234
234
  Everything already recorded stays with them. Tracking carries on as before:
235
235
  same visitor id, same affiliate. If a different user signs in without a
236
- sign-out in between, the browser switches to them on its own.
236
+ sign-out in between, the browser switches to them on its own, and the last
237
+ person's Live Chat thread is forgotten first.
237
238
 
238
239
  ## Live Chat
239
240
 
@@ -254,7 +255,7 @@ import { openChat } from '@affiliateo/web'
254
255
  // Never load the chat
255
256
  <AffiliateoProvider appId="..." chat={false}>
256
257
 
257
- // Your own look, over the dashboard's (4.15.0); leave out what the dashboard should decide
258
+ // Your own look, over the dashboard's (4.9.0); leave out what the dashboard should decide
258
259
  <AffiliateoProvider appId="..." chatLook={{ color: '#7C3AED', iconColor: '#FFFFFF', sendColor: '#007AFF', placeholder: 'Ask us anything', style: 'glass', corner: 'bottom-left', size: 56 }}>
259
260
  ```
260
261
 
@@ -269,6 +270,18 @@ the script tag, `data-chat-color`, `data-chat-icon-color`, `data-chat-send-color
269
270
 
270
271
  ## Changelog
271
272
 
273
+ - **4.9.2:** events are sent as plain text, so the browser no longer makes an
274
+ extra check request before each one. Nothing to change in your code.
275
+ - **4.9.1:** a different user signing in with `identify()` (no `logout()` in
276
+ between) now forgets the last person's Live Chat thread first, as a sign-out
277
+ does, and the bubble learns the new user without a reload. Signing in from
278
+ anonymous keeps the person's own thread. With it, from the bubble itself
279
+ (chat.js, served by Affiliateo, so every site gets it with no update): its
280
+ screen-reader labels in the visitor's language, the visitor's chat key no
281
+ longer in the live connection's address (a one-minute ticket instead), only
282
+ the page's address without its query or fragment sent to the inbox, and a
283
+ size set in code kept within 40 to 80.
284
+
272
285
  - **4.9.0:** Live Chat (see above): the chat bubble loads by itself once the
273
286
  business switches it on; new `chat` prop and `openChat()`, `closeChat()`,
274
287
  `showChatBubble()`, `hideChatBubble()`. `logout()` and `reset()` forget the
package/dist/chat.d.ts CHANGED
@@ -18,6 +18,12 @@ export interface LoadLiveChatOptions {
18
18
  visitorId: string | null;
19
19
  /** The signed-in user id from identify(). Null while opted out. */
20
20
  userId: string | null;
21
+ /**
22
+ * Read again just before chat.js is added (4.9.1): a sign-in or a switch
23
+ * of person while the config check is on its way must not load the bubble
24
+ * with the last person's id. Falls back to `userId`.
25
+ */
26
+ getUserId?: () => string | null;
21
27
  /** 'hidden' loads the chat without a bubble, for a site that opens it from its own button. */
22
28
  bubble?: 'visible' | 'hidden';
23
29
  theme?: 'light' | 'dark';
@@ -34,6 +40,13 @@ export declare function closeChat(): void;
34
40
  export declare function showChatBubble(): void;
35
41
  /** Hide the chat bubble, and close the window. */
36
42
  export declare function hideChatBubble(): void;
43
+ /**
44
+ * Who is signed in now (identify(), 4.9.1). chat.js reads the user once, when
45
+ * it loads: a sign-in after that reaches it through __setUser, which also
46
+ * forgets a DIFFERENT person's thread (signing in from anonymous keeps it).
47
+ * Still loading: it reads data-user when it runs, so that is updated.
48
+ */
49
+ export declare function setLiveChatUser(userId: string): void;
37
50
  /**
38
51
  * The person signed out (logout(), reset()): their chat goes, so whoever
39
52
  * signs in next starts a new thread. The token chat.js keeps goes from
package/dist/chat.js CHANGED
@@ -54,8 +54,9 @@ export function loadLiveChat(opts) {
54
54
  script.setAttribute('data-app', opts.appId);
55
55
  if (opts.visitorId)
56
56
  script.setAttribute('data-visitor', opts.visitorId);
57
- if (opts.userId)
58
- script.setAttribute('data-user', opts.userId.slice(0, 128));
57
+ const userId = opts.getUserId ? opts.getUserId() : opts.userId;
58
+ if (userId)
59
+ script.setAttribute('data-user', userId.slice(0, 128));
59
60
  if (opts.bubble === 'hidden')
60
61
  script.setAttribute('data-bubble', 'hidden');
61
62
  if (opts.theme)
@@ -96,6 +97,22 @@ export function showChatBubble() {
96
97
  export function hideChatBubble() {
97
98
  chatGlobal()?.hide?.();
98
99
  }
100
+ /**
101
+ * Who is signed in now (identify(), 4.9.1). chat.js reads the user once, when
102
+ * it loads: a sign-in after that reaches it through __setUser, which also
103
+ * forgets a DIFFERENT person's thread (signing in from anonymous keeps it).
104
+ * Still loading: it reads data-user when it runs, so that is updated.
105
+ */
106
+ export function setLiveChatUser(userId) {
107
+ if (typeof window === 'undefined' || typeof document === 'undefined')
108
+ return;
109
+ const chat = chatGlobal();
110
+ if (chat?.__setUser) {
111
+ chat.__setUser(userId);
112
+ return;
113
+ }
114
+ document.querySelector('script[data-affiliateo-chat]')?.setAttribute('data-user', userId.slice(0, 128));
115
+ }
99
116
  /**
100
117
  * The person signed out (logout(), reset()): their chat goes, so whoever
101
118
  * signs in next starts a new thread. The token chat.js keeps goes from
package/dist/provider.js CHANGED
@@ -6,7 +6,7 @@ import { isLikelyBot } from './bot-detect';
6
6
  import { isDevHost } from './dev-host';
7
7
  import { mintTurnstileToken, consumeTurnstileToken } from './turnstile';
8
8
  import { usableUserId } from './user-id';
9
- import { forgetLiveChat, loadLiveChat } from './chat';
9
+ import { forgetLiveChat, loadLiveChat, setLiveChatUser } from './chat';
10
10
  const DEFAULT_API_URL = 'https://affiliateo.com';
11
11
  const LS_VISITOR_ID = 'affiliateo_visitor_id';
12
12
  const LS_REF = 'affiliateo_ref';
@@ -336,7 +336,15 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
336
336
  log('identify skipped: not a user id (empty, over 128 characters, or a placeholder like "null", "guest" or "0")', { user_id: userId });
337
337
  return;
338
338
  }
339
+ // A DIFFERENT person signing in on this browser (4.9.1): the last
340
+ // person's Live Chat thread was theirs, so it goes first, as on a
341
+ // sign-out. Signing in from anonymous keeps it (the same person who
342
+ // was writing). chat.js is told who is signed in now either way.
343
+ const previous = usableUserId(lsGet(LS_USER_ID));
344
+ if (previous && previous !== uid)
345
+ forgetLiveChat(appIdRef.current);
339
346
  lsSet(LS_USER_ID, uid);
347
+ setLiveChatUser(uid);
340
348
  const cid = appIdRef.current;
341
349
  const vid = visitorIdRef.current;
342
350
  const queue = queueRef.current;
@@ -1042,6 +1050,8 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
1042
1050
  appId: resolvedAppId,
1043
1051
  visitorId: optedOut ? null : visitorIdRef.current ?? lsGet(LS_VISITOR_ID),
1044
1052
  userId: optedOut ? null : usableUserId(lsGet(LS_USER_ID)),
1053
+ // Read again when the bubble is added: a sign-in meanwhile counts.
1054
+ getUserId: () => (isOptedOut() ? null : usableUserId(lsGet(LS_USER_ID))),
1045
1055
  bubble: chat === 'hidden' ? 'hidden' : 'visible',
1046
1056
  look: chatLook,
1047
1057
  log,
package/dist/queue.js CHANGED
@@ -46,6 +46,10 @@ 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';
49
53
  function lsGet(key) {
50
54
  try {
51
55
  return localStorage.getItem(key);
@@ -188,7 +192,7 @@ export class EventQueue {
188
192
  try {
189
193
  const res = await fetch(event.endpoint, {
190
194
  method: 'POST',
191
- headers: { 'Content-Type': 'application/json' },
195
+ headers: { 'Content-Type': BODY_TYPE },
192
196
  body: JSON.stringify(event.payload),
193
197
  // keepalive lets the request survive a navigation. paired
194
198
  // with the queue so even if this fetch errors, we'll retry.
@@ -243,7 +247,7 @@ export class EventQueue {
243
247
  try {
244
248
  fetch(event.endpoint, {
245
249
  method: 'POST',
246
- headers: { 'Content-Type': 'application/json' },
250
+ headers: { 'Content-Type': BODY_TYPE },
247
251
  body: JSON.stringify(event.payload),
248
252
  keepalive: true,
249
253
  }).catch(() => { });
@@ -256,7 +260,7 @@ export class EventQueue {
256
260
  }
257
261
  for (const event of this.queue) {
258
262
  try {
259
- navigator.sendBeacon(event.endpoint, new Blob([JSON.stringify(event.payload)], { type: 'application/json' }));
263
+ navigator.sendBeacon(event.endpoint, new Blob([JSON.stringify(event.payload)], { type: BODY_TYPE }));
260
264
  }
261
265
  catch { /* swallow */ }
262
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@affiliateo/web",
3
- "version": "4.9.0",
3
+ "version": "4.9.2",
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",