@affiliateo/web 4.8.0 → 4.9.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/README.md +39 -1
- package/dist/chat.d.ts +43 -0
- package/dist/chat.js +117 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +24 -3
- package/dist/types.d.ts +26 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -229,13 +229,51 @@ async function handleSignOut() {
|
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
The browser then counts as a new anonymous customer of your app, so whoever
|
|
232
|
-
signs in next (or nobody) isn't counted as the person who just left.
|
|
232
|
+
signs in next (or nobody) isn't counted as the person who just left. Their Live
|
|
233
|
+
Chat goes too: the next person to open the chat starts a new thread.
|
|
233
234
|
Everything already recorded stays with them. Tracking carries on as before:
|
|
234
235
|
same visitor id, same affiliate. If a different user signs in without a
|
|
235
236
|
sign-out in between, the browser switches to them on its own.
|
|
236
237
|
|
|
238
|
+
## Live Chat
|
|
239
|
+
|
|
240
|
+
Let visitors chat with you in real time; you answer from the Live Chat inbox in
|
|
241
|
+
your Affiliateo dashboard. Add **Live Chat** from the App Store in your dashboard
|
|
242
|
+
and switch it on for this app: the chat bubble then shows on your site by itself,
|
|
243
|
+
with nothing else to install. Visitors don't need an account.
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import { openChat } from '@affiliateo/web'
|
|
247
|
+
|
|
248
|
+
// Open the chat from your own button
|
|
249
|
+
<button onClick={openChat}>Chat with us</button>
|
|
250
|
+
|
|
251
|
+
// No bubble, only your own button
|
|
252
|
+
<AffiliateoProvider appId="..." chat="hidden">
|
|
253
|
+
|
|
254
|
+
// Never load the chat
|
|
255
|
+
<AffiliateoProvider appId="..." chat={false}>
|
|
256
|
+
|
|
257
|
+
// Your own look, over the dashboard's (4.15.0); leave out what the dashboard should decide
|
|
258
|
+
<AffiliateoProvider appId="..." chatLook={{ color: '#7C3AED', iconColor: '#FFFFFF', sendColor: '#007AFF', placeholder: 'Ask us anything', style: 'glass', corner: 'bottom-left', size: 56 }}>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The bubble can be dragged anywhere and remembers where it was left, shows a red
|
|
262
|
+
number for unread replies and a green dot while someone on your team is online,
|
|
263
|
+
and opens the chat beside it (full screen on a phone). The plain `<script>` snippet
|
|
264
|
+
(t.js) does the same; there, `data-chat="off"` or `data-chat-bubble="hidden"` on
|
|
265
|
+
the script tag, `data-chat-color`, `data-chat-icon-color`, `data-chat-send-color`,
|
|
266
|
+
`data-chat-style`, `data-chat-corner`, `data-chat-size` and `data-chat-placeholder`
|
|
267
|
+
(the message box text, in your own words and language) for your own look, and
|
|
268
|
+
`AffiliateoChat.open()` from your own button.
|
|
269
|
+
|
|
237
270
|
## Changelog
|
|
238
271
|
|
|
272
|
+
- **4.9.0:** Live Chat (see above): the chat bubble loads by itself once the
|
|
273
|
+
business switches it on; new `chat` prop and `openChat()`, `closeChat()`,
|
|
274
|
+
`showChatBubble()`, `hideChatBubble()`. `logout()` and `reset()` forget the
|
|
275
|
+
person's chat.
|
|
276
|
+
|
|
239
277
|
- **4.8.0:** new `logout()` for sign-out. `reset()` is now the same as
|
|
240
278
|
`logout()`: it used to mint a new visitor id and drop the affiliate ref,
|
|
241
279
|
which split one person into two visitors. `identify()` skips placeholder ids,
|
package/dist/chat.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Chat (4.9.0): the chat bubble on a website that uses this package
|
|
3
|
+
* instead of the t.js snippet. Same behaviour as the snippet: once the
|
|
4
|
+
* business switches Live Chat on for this app in its Affiliateo dashboard,
|
|
5
|
+
* the bubble shows by itself. It is Affiliateo's own chat.js, loaded from the
|
|
6
|
+
* same origin as the API, so the website bubble looks and behaves the same
|
|
7
|
+
* whichever way a site installed Affiliateo.
|
|
8
|
+
*
|
|
9
|
+
* Asked once per visit: a site with the chat off is remembered for ten
|
|
10
|
+
* minutes in this tab (the same key t.js uses), so it costs one small request
|
|
11
|
+
* per visit, not per page.
|
|
12
|
+
*/
|
|
13
|
+
import type { ChatLook } from './types';
|
|
14
|
+
export interface LoadLiveChatOptions {
|
|
15
|
+
apiBase: string;
|
|
16
|
+
appId: string;
|
|
17
|
+
/** The tracking visitor, so the inbox names them the way the tracking pages do. Null while opted out. */
|
|
18
|
+
visitorId: string | null;
|
|
19
|
+
/** The signed-in user id from identify(). Null while opted out. */
|
|
20
|
+
userId: string | null;
|
|
21
|
+
/** 'hidden' loads the chat without a bubble, for a site that opens it from its own button. */
|
|
22
|
+
bubble?: 'visible' | 'hidden';
|
|
23
|
+
theme?: 'light' | 'dark';
|
|
24
|
+
/** The site's own look for the bubble, over the dashboard's. */
|
|
25
|
+
look?: ChatLook;
|
|
26
|
+
log: (msg: string, data?: unknown) => void;
|
|
27
|
+
}
|
|
28
|
+
export declare function loadLiveChat(opts: LoadLiveChatOptions): void;
|
|
29
|
+
/** Open the chat window, from your own button. Does nothing while the chat is off or still loading. */
|
|
30
|
+
export declare function openChat(): void;
|
|
31
|
+
/** Close the chat window. */
|
|
32
|
+
export declare function closeChat(): void;
|
|
33
|
+
/** Show the chat bubble (after hideChatBubble, or with chat="hidden"). */
|
|
34
|
+
export declare function showChatBubble(): void;
|
|
35
|
+
/** Hide the chat bubble, and close the window. */
|
|
36
|
+
export declare function hideChatBubble(): void;
|
|
37
|
+
/**
|
|
38
|
+
* The person signed out (logout(), reset()): their chat goes, so whoever
|
|
39
|
+
* signs in next starts a new thread. The token chat.js keeps goes from
|
|
40
|
+
* localStorage even while chat.js is still loading; once it has loaded, it
|
|
41
|
+
* also closes the window and clears the red number.
|
|
42
|
+
*/
|
|
43
|
+
export declare function forgetLiveChat(appId: string): void;
|
package/dist/chat.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Chat (4.9.0): the chat bubble on a website that uses this package
|
|
3
|
+
* instead of the t.js snippet. Same behaviour as the snippet: once the
|
|
4
|
+
* business switches Live Chat on for this app in its Affiliateo dashboard,
|
|
5
|
+
* the bubble shows by itself. It is Affiliateo's own chat.js, loaded from the
|
|
6
|
+
* same origin as the API, so the website bubble looks and behaves the same
|
|
7
|
+
* whichever way a site installed Affiliateo.
|
|
8
|
+
*
|
|
9
|
+
* Asked once per visit: a site with the chat off is remembered for ten
|
|
10
|
+
* minutes in this tab (the same key t.js uses), so it costs one small request
|
|
11
|
+
* per visit, not per page.
|
|
12
|
+
*/
|
|
13
|
+
const OFF_FOR_MS = 10 * 60 * 1000;
|
|
14
|
+
function chatGlobal() {
|
|
15
|
+
if (typeof window === 'undefined')
|
|
16
|
+
return undefined;
|
|
17
|
+
return window.AffiliateoChat;
|
|
18
|
+
}
|
|
19
|
+
export function loadLiveChat(opts) {
|
|
20
|
+
if (typeof window === 'undefined' || typeof document === 'undefined' || typeof fetch !== 'function')
|
|
21
|
+
return;
|
|
22
|
+
if (!opts.appId || chatGlobal()?.__loaded)
|
|
23
|
+
return;
|
|
24
|
+
if (document.querySelector('script[data-affiliateo-chat]'))
|
|
25
|
+
return;
|
|
26
|
+
const offKey = `affiliateo_chat_off:${opts.appId}`;
|
|
27
|
+
try {
|
|
28
|
+
const off = sessionStorage.getItem(offKey);
|
|
29
|
+
if (off && Date.now() - Number(off) < OFF_FOR_MS)
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* storage refused: ask */
|
|
34
|
+
}
|
|
35
|
+
fetch(`${opts.apiBase}/api/live-chat/config?app=${encodeURIComponent(opts.appId)}`)
|
|
36
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
37
|
+
.then((config) => {
|
|
38
|
+
if (!config || !config.enabled) {
|
|
39
|
+
try {
|
|
40
|
+
sessionStorage.setItem(offKey, String(Date.now()));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* storage refused */
|
|
44
|
+
}
|
|
45
|
+
opts.log('chat: off for this app');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (chatGlobal()?.__loaded || document.querySelector('script[data-affiliateo-chat]'))
|
|
49
|
+
return;
|
|
50
|
+
const script = document.createElement('script');
|
|
51
|
+
script.src = `${opts.apiBase}/chat.js`;
|
|
52
|
+
script.async = true;
|
|
53
|
+
script.setAttribute('data-affiliateo-chat', '');
|
|
54
|
+
script.setAttribute('data-app', opts.appId);
|
|
55
|
+
if (opts.visitorId)
|
|
56
|
+
script.setAttribute('data-visitor', opts.visitorId);
|
|
57
|
+
if (opts.userId)
|
|
58
|
+
script.setAttribute('data-user', opts.userId.slice(0, 128));
|
|
59
|
+
if (opts.bubble === 'hidden')
|
|
60
|
+
script.setAttribute('data-bubble', 'hidden');
|
|
61
|
+
if (opts.theme)
|
|
62
|
+
script.setAttribute('data-theme', opts.theme);
|
|
63
|
+
const look = opts.look;
|
|
64
|
+
if (look?.color)
|
|
65
|
+
script.setAttribute('data-color', look.color);
|
|
66
|
+
if (look?.iconColor)
|
|
67
|
+
script.setAttribute('data-icon-color', look.iconColor);
|
|
68
|
+
if (look?.sendColor)
|
|
69
|
+
script.setAttribute('data-send-color', look.sendColor);
|
|
70
|
+
if (look?.placeholder)
|
|
71
|
+
script.setAttribute('data-placeholder', look.placeholder.slice(0, 60));
|
|
72
|
+
if (look?.style)
|
|
73
|
+
script.setAttribute('data-style', look.style);
|
|
74
|
+
if (look?.corner)
|
|
75
|
+
script.setAttribute('data-corner', look.corner);
|
|
76
|
+
if (look?.size)
|
|
77
|
+
script.setAttribute('data-size', String(look.size));
|
|
78
|
+
(document.head || document.documentElement).appendChild(script);
|
|
79
|
+
opts.log('chat: loading the bubble');
|
|
80
|
+
})
|
|
81
|
+
.catch(() => opts.log('chat: config check failed (network)'));
|
|
82
|
+
}
|
|
83
|
+
/** Open the chat window, from your own button. Does nothing while the chat is off or still loading. */
|
|
84
|
+
export function openChat() {
|
|
85
|
+
chatGlobal()?.open?.();
|
|
86
|
+
}
|
|
87
|
+
/** Close the chat window. */
|
|
88
|
+
export function closeChat() {
|
|
89
|
+
chatGlobal()?.close?.();
|
|
90
|
+
}
|
|
91
|
+
/** Show the chat bubble (after hideChatBubble, or with chat="hidden"). */
|
|
92
|
+
export function showChatBubble() {
|
|
93
|
+
chatGlobal()?.show?.();
|
|
94
|
+
}
|
|
95
|
+
/** Hide the chat bubble, and close the window. */
|
|
96
|
+
export function hideChatBubble() {
|
|
97
|
+
chatGlobal()?.hide?.();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The person signed out (logout(), reset()): their chat goes, so whoever
|
|
101
|
+
* signs in next starts a new thread. The token chat.js keeps goes from
|
|
102
|
+
* localStorage even while chat.js is still loading; once it has loaded, it
|
|
103
|
+
* also closes the window and clears the red number.
|
|
104
|
+
*/
|
|
105
|
+
export function forgetLiveChat(appId) {
|
|
106
|
+
if (typeof window === 'undefined')
|
|
107
|
+
return;
|
|
108
|
+
if (appId) {
|
|
109
|
+
try {
|
|
110
|
+
localStorage.removeItem(`affiliateo_chat_token:${appId}`);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* storage refused */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
chatGlobal()?.__forget?.();
|
|
117
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { AffiliateoProvider, useAffiliateRef, getAttribution } from './provider';
|
|
2
|
-
export
|
|
2
|
+
export { openChat, closeChat, showChatBubble, hideChatBubble } from './chat';
|
|
3
|
+
export type { AffiliateoConfig, AffiliateoContextValue, ChatLook } from './types';
|
package/dist/index.js
CHANGED
package/dist/provider.d.ts
CHANGED
|
@@ -18,6 +18,6 @@ export declare function useAffiliateRef(): AffiliateoContextValue;
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export declare function getAttribution(): Record<string, string>;
|
|
21
|
-
export declare function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, children, }: AffiliateoConfig & {
|
|
21
|
+
export declare function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, chat, chatLook, children, }: AffiliateoConfig & {
|
|
22
22
|
children: React.ReactNode;
|
|
23
23
|
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/provider.js
CHANGED
|
@@ -6,6 +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
10
|
const DEFAULT_API_URL = 'https://affiliateo.com';
|
|
10
11
|
const LS_VISITOR_ID = 'affiliateo_visitor_id';
|
|
11
12
|
const LS_REF = 'affiliateo_ref';
|
|
@@ -195,7 +196,7 @@ let providerMountCount = 0;
|
|
|
195
196
|
function isProductionBuild() {
|
|
196
197
|
return typeof process !== 'undefined' && process?.env?.NODE_ENV === 'production';
|
|
197
198
|
}
|
|
198
|
-
export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, children, }) {
|
|
199
|
+
export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, chat, chatLook, children, }) {
|
|
199
200
|
// Normalize autoCapturePageviews. Three shapes:
|
|
200
201
|
// - undefined / true / object → auto-capture ON (with object overrides)
|
|
201
202
|
// - false → auto-capture OFF entirely (host must call trackPageview)
|
|
@@ -365,8 +366,8 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
|
|
|
365
366
|
// browser starts over as a new anonymous customer of the app, so whoever
|
|
366
367
|
// signs in next (or nobody) is not counted as the person who just left,
|
|
367
368
|
// and everything already recorded stays with them. Forgets the stored
|
|
368
|
-
// user id. The visitor id, the affiliate ref, the
|
|
369
|
-
// itself all carry on unchanged.
|
|
369
|
+
// user id and their Live Chat. The visitor id, the affiliate ref, the
|
|
370
|
+
// session and tracking itself all carry on unchanged.
|
|
370
371
|
const logout = useCallback(async () => {
|
|
371
372
|
try {
|
|
372
373
|
if (typeof window === 'undefined')
|
|
@@ -374,6 +375,7 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
|
|
|
374
375
|
log('logout');
|
|
375
376
|
lsRemove(LS_USER_ID);
|
|
376
377
|
lsRemove(LS_USER_SENT);
|
|
378
|
+
forgetLiveChat(appIdRef.current);
|
|
377
379
|
if (isOptedOut())
|
|
378
380
|
return;
|
|
379
381
|
const cid = appIdRef.current;
|
|
@@ -1028,5 +1030,24 @@ export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames
|
|
|
1028
1030
|
// The boot configuration comes off bootRef above, so this list is what
|
|
1029
1031
|
// legitimately rebuilds the pipeline.
|
|
1030
1032
|
}, [apiBase, allowedHostnames, allowLocalhost, allowIframe, log, track, identify, trackPageview, reset, logout, optOut, optIn, flush]);
|
|
1033
|
+
// Live Chat (4.9.0, chat.ts). After the tracking effect above has read or
|
|
1034
|
+
// minted the visitor id, so the inbox can name the visitor. Neither the
|
|
1035
|
+
// visitor nor the signed-in user goes along while tracking is opted out.
|
|
1036
|
+
useEffect(() => {
|
|
1037
|
+
if (chat === false || !resolvedAppId)
|
|
1038
|
+
return;
|
|
1039
|
+
const optedOut = isOptedOut();
|
|
1040
|
+
loadLiveChat({
|
|
1041
|
+
apiBase,
|
|
1042
|
+
appId: resolvedAppId,
|
|
1043
|
+
visitorId: optedOut ? null : visitorIdRef.current ?? lsGet(LS_VISITOR_ID),
|
|
1044
|
+
userId: optedOut ? null : usableUserId(lsGet(LS_USER_ID)),
|
|
1045
|
+
bubble: chat === 'hidden' ? 'hidden' : 'visible',
|
|
1046
|
+
look: chatLook,
|
|
1047
|
+
log,
|
|
1048
|
+
});
|
|
1049
|
+
// The look is read when the bubble loads.
|
|
1050
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1051
|
+
}, [chat, resolvedAppId, apiBase, log]);
|
|
1031
1052
|
return (_jsx(AffiliateoContext.Provider, { value: state, children: children }));
|
|
1032
1053
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -80,6 +80,17 @@ export interface AffiliateoConfig {
|
|
|
80
80
|
/** Pageview auto-capture behavior. Pass `false` to disable entirely
|
|
81
81
|
* (you'll call `trackPageview()` yourself). Pass an object to fine-tune.
|
|
82
82
|
* Defaults to `true` (auto-capture everything). */
|
|
83
|
+
/** Live Chat (4.9.0). Once the business switches Live Chat on for this
|
|
84
|
+
* app in its Affiliateo dashboard, a chat bubble shows on the site.
|
|
85
|
+
* `true` (the default) shows it; `'hidden'` loads the chat without a
|
|
86
|
+
* bubble, for a site that calls `openChat()` from its own button; `false`
|
|
87
|
+
* never loads it. */
|
|
88
|
+
chat?: boolean | 'hidden';
|
|
89
|
+
/** Your own look for the chat bubble (4.15.0), over the Live Chat settings
|
|
90
|
+
* in your dashboard: anything left out keeps the dashboard's choice.
|
|
91
|
+
* sendColor colors the send button and the person's own messages;
|
|
92
|
+
* placeholder is the message box text, in your own words and language. */
|
|
93
|
+
chatLook?: ChatLook;
|
|
83
94
|
autoCapturePageviews?: boolean | {
|
|
84
95
|
/** Fire the initial pageview on mount. Defaults to true. Set false
|
|
85
96
|
* for cookie-consent flows where you want to defer until the user
|
|
@@ -152,7 +163,8 @@ export interface AffiliateoContextValue {
|
|
|
152
163
|
/** The user signed out (4.8.0). Call it on sign-out: this browser starts
|
|
153
164
|
* over as a new anonymous customer of the app, so whoever signs in next
|
|
154
165
|
* (or nobody) is not counted as the person who just left, and everything
|
|
155
|
-
* already recorded stays with them. Forgets the stored user id
|
|
166
|
+
* already recorded stays with them. Forgets the stored user id, and
|
|
167
|
+
* their Live Chat: the next person to open it starts a new thread. The
|
|
156
168
|
* visitor id, the affiliate ref and tracking carry on unchanged.
|
|
157
169
|
*
|
|
158
170
|
* Example:
|
|
@@ -209,3 +221,16 @@ export interface AffiliateoContextValue {
|
|
|
209
221
|
* flush). Safe to await without timeout concerns. */
|
|
210
222
|
flush: () => Promise<void>;
|
|
211
223
|
}
|
|
224
|
+
/** The chat bubble's look, set in code (4.15.0). Colors are hex ("#7C3AED"). */
|
|
225
|
+
export interface ChatLook {
|
|
226
|
+
color?: string;
|
|
227
|
+
iconColor?: string;
|
|
228
|
+
/** The chat's send button and the person's own messages (iMessage blue unless the dashboard says otherwise). */
|
|
229
|
+
sendColor?: string;
|
|
230
|
+
/** The message box text, in your own words and language (up to 60 characters): pass your app's own translation. */
|
|
231
|
+
placeholder?: string;
|
|
232
|
+
style?: 'solid' | 'glass';
|
|
233
|
+
corner?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
234
|
+
/** In pixels, 40 to 80. */
|
|
235
|
+
size?: number;
|
|
236
|
+
}
|