From c1c5dd3baba7dff8768d130096758a486d5ae16e Mon Sep 17 00:00:00 2001 From: Andrei Soroker Date: Sat, 21 Oct 2023 14:34:14 -0700 Subject: [PATCH] Visitor (anon user) support --- .changeset/shaggy-eyes-film.md | 6 + packages/fogbender-proto/src/client.ts | 28 ++++- .../src/context/sharedRoster.tsx | 26 ++-- packages/fogbender-proto/src/context/ws.tsx | 29 ++++- packages/fogbender-proto/src/schema.ts | 64 +++++++++- packages/fogbender-proto/src/useServerWs.tsx | 113 +++++++++++++++++- packages/fogbender/src/checkToken.ts | 10 +- packages/fogbender/src/createIframe.ts | 6 + packages/fogbender/src/floatingWidget.tsx | 48 +++++--- packages/fogbender/src/index.ts | 106 +++++++++++++--- packages/fogbender/src/server.ts | 2 +- packages/fogbender/src/types.ts | 21 ++++ 12 files changed, 395 insertions(+), 64 deletions(-) create mode 100644 .changeset/shaggy-eyes-film.md diff --git a/.changeset/shaggy-eyes-film.md b/.changeset/shaggy-eyes-film.md new file mode 100644 index 00000000..a0c80911 --- /dev/null +++ b/.changeset/shaggy-eyes-film.md @@ -0,0 +1,6 @@ +--- +"fogbender": minor +"fogbender-proto": minor +--- + +Visitor (anon user) support diff --git a/packages/fogbender-proto/src/client.ts b/packages/fogbender-proto/src/client.ts index 75f635ff..1211138a 100644 --- a/packages/fogbender-proto/src/client.ts +++ b/packages/fogbender-proto/src/client.ts @@ -4,15 +4,31 @@ import type { AnyToken } from "./schema"; export type ErrorType = "error" | "warning" | "other"; export type ErrorKind = "server_stopped_responding" | "other"; +export type ClientSession = { + sessionId: string; + userId?: string; + helpdeskId?: string; + userAvatarUrl?: string; + userName?: string; + userEmail?: string; + customerName?: string; +}; + +export type VisitorInfo = { + widgetId: string; + token: string; + userId: string; +}; + export interface Client { getEnv?(): Env | undefined; getServerApiUrl?(): string | undefined; onError?(type: ErrorType, kind: ErrorKind, ...errors: (Error | string)[]): void; - setSession?( - sessionId: string, - userId?: string, - helpdeskId?: string, - userAvatarUrl?: string - ): void; + setSession?(x: ClientSession): void; + + getVisitorInfo?(widgetId: string): VisitorInfo | undefined; + + setVisitorInfo?(x: VisitorInfo, reload?: boolean): void; + onWrongToken?(token: AnyToken): void; } diff --git a/packages/fogbender-proto/src/context/sharedRoster.tsx b/packages/fogbender-proto/src/context/sharedRoster.tsx index e2feb1e4..5ca65267 100644 --- a/packages/fogbender-proto/src/context/sharedRoster.tsx +++ b/packages/fogbender-proto/src/context/sharedRoster.tsx @@ -43,6 +43,7 @@ export const useSharedRosterInternal = ({ helpdeskId?: string; userId?: string; }) => { + const ourId = userId; const { serverCall, lastIncomingMessageAtom } = ws; const lastIncomingMessage = useAtomValue(lastIncomingMessageAtom); const { isRosterReadyAtom, rosterViewSectionsAtom, rosterSectionsActionsAtom, rosterRoomFamily } = @@ -120,8 +121,8 @@ export const useSharedRosterInternal = ({ return; } // TODO maybe there's a better way to tell users and agents apart? - if (userId) { - const topic = userId.startsWith("a") ? `agent/${userId}/badges` : `user/${userId}/badges`; + if (ourId) { + const topic = ourId.startsWith("a") ? `agent/${ourId}/badges` : `user/${ourId}/badges`; serverCall({ msgType: "Stream.Sub", topic, @@ -129,15 +130,15 @@ export const useSharedRosterInternal = ({ console.assert(x.msgType === "Stream.SubOk"); }); } - }, [fogSessionId, userId, serverCall]); + }, [fogSessionId, ourId, serverCall]); React.useEffect(() => { if (!fogSessionId) { return; } - if (userId && !badgesLoaded) { + if (ourId && !badgesLoaded) { // TODO maybe there's a better way to tell users and agents apart? - const topic = userId.startsWith("a") ? `agent/${userId}/badges` : `user/${userId}/badges`; + const topic = ourId.startsWith("a") ? `agent/${ourId}/badges` : `user/${ourId}/badges`; serverCall({ msgType: "Stream.Get", topic, @@ -158,27 +159,27 @@ export const useSharedRosterInternal = ({ }) .catch(() => {}); } - }, [fogSessionId, userId, badgesPrevCursor, badgesLoaded, updateBadges, serverCall]); + }, [fogSessionId, ourId, badgesPrevCursor, badgesLoaded, updateBadges, serverCall]); const updateRoster = React.useCallback( (roomsIn: EventRoom[], rosterRooms: EventRosterRoom[]) => { if (rosterRooms.length > 0) { dispatchRosterSections({ action: "update_roster", rosterRooms }); } - if (userId && roomsIn.length > 0) { + if (ourId && roomsIn.length > 0) { setRawRoster(roster => { let newRoster = roster; roomsIn.forEach(room => { newRoster = newRoster.filter(x => room.id !== x.id); if (!room.remove) { - newRoster.push(eventRoomToRoom(room, userId)); + newRoster.push(eventRoomToRoom(room, ourId)); } }); return newRoster; }); } }, - [userId] + [ourId] ); onRoomRef.current = updateRoster; @@ -276,9 +277,9 @@ export const useSharedRosterInternal = ({ if (!fogSessionId) { return; } - if (userId) { + if (ourId) { // TODO maybe there's a better way to tell users and agents apart? - const topic = userId.startsWith("a") ? `agent/${userId}/seen` : `user/${userId}/seen`; + const topic = ourId.startsWith("a") ? `agent/${ourId}/seen` : `user/${ourId}/seen`; serverCall({ msgType: "Stream.Sub", topic, @@ -286,7 +287,7 @@ export const useSharedRosterInternal = ({ console.assert(x.msgType === "Stream.SubOk"); }); } - }, [fogSessionId, userId, serverCall]); + }, [fogSessionId, ourId, serverCall]); const updateCustomers = React.useCallback((customersIn: EventCustomer[]) => { setCustomers(customers => { @@ -387,6 +388,7 @@ export const useSharedRosterInternal = ({ rosterViewSectionsAtom, rosterSectionsActionsAtom, rosterRoomFamily, + ourId, }; }, [roster, roomById, badges, customers]); }; diff --git a/packages/fogbender-proto/src/context/ws.tsx b/packages/fogbender-proto/src/context/ws.tsx index 37a29bec..5b48a299 100644 --- a/packages/fogbender-proto/src/context/ws.tsx +++ b/packages/fogbender-proto/src/context/ws.tsx @@ -36,6 +36,7 @@ export type Author = { id: string; name: string; type: AuthorType; + userType?: "visitor-verified" | "visitor-unverified" | "user"; avatarUrl?: string; }; @@ -122,7 +123,15 @@ function useProviderValue( const [userAvatarUrl, setUserAvatarUrl] = React.useState(); const [providerClient] = React.useState(() => ({ ...client, - setSession(sessionId, userId, helpdeskId, userAvatarUrl) { + setSession({ + sessionId, + userId, + helpdeskId, + userAvatarUrl, + userName, + userEmail, + customerName, + }) { setFogSessionId(sessionId); if (userId) { setUserId(userId); @@ -133,7 +142,15 @@ function useProviderValue( } setHelpdeskId(helpdeskId); - client?.setSession?.(sessionId, userId, helpdeskId, userAvatarUrl); + client?.setSession?.({ + sessionId, + userId, + helpdeskId, + userAvatarUrl, + userName, + userEmail, + customerName, + }); }, })); React.useEffect(() => { @@ -173,7 +190,9 @@ function useProviderValue( return React.useMemo(() => { return { + client: client, serverCall: ws.serverCall, + widgetId: ws.widgetId, lastIncomingMessageAtom: ws.lastIncomingMessageAtom, sharedRosterAtom, respondToMessage: ws.respondToMessage, @@ -182,6 +201,7 @@ function useProviderValue( isAuthenticated: ws.isAuthenticated, isTokenWrong: ws.isTokenWrong, isAgent: ws.isAgent, + userType: ws.userType, avatarLibraryUrl: ws.avatarLibraryUrl, token, fogSessionId, @@ -190,8 +210,11 @@ function useProviderValue( workspaceId, userAvatarUrl, agentRole: ws.agentRole, + visitorJWT: ws.visitorJWT, }; }, [ + client, + ws.widgetId, ws.serverCall, ws.lastIncomingMessageAtom, sharedRosterAtom, @@ -201,7 +224,9 @@ function useProviderValue( ws.isAuthenticated, ws.isTokenWrong, ws.isAgent, + ws.userType, ws.avatarLibraryUrl, + ws.visitorJWT, token, fogSessionId, userId, diff --git a/packages/fogbender-proto/src/schema.ts b/packages/fogbender-proto/src/schema.ts index 7d4ab5ad..c8fe5949 100644 --- a/packages/fogbender-proto/src/schema.ts +++ b/packages/fogbender-proto/src/schema.ts @@ -21,7 +21,17 @@ export type AgentToken = { versions?: { [key: string]: string }; }; -export type AnyToken = UserToken | AgentToken; +export type VisitorToken = { + widgetId: string; + visitor: true; + userId?: string; + visitorKey?: string; + visitorToken?: string; + visitUrl?: string; + versions?: { [key: string]: string }; +}; + +export type AnyToken = UserToken | AgentToken | VisitorToken; // UTILITY TYPES @@ -75,6 +85,10 @@ export type APISchema = { MessageRefreshFilesRPC: RPC; AuthUserRPC: RPC; AuthAgentRPC: RPC; + AuthVisitorRPC: RPC; + VisitorNewRPC: RPC; + VisitorVerifyEmailRPC: RPC; + VisitorVerifyCodeRPC: RPC; EchoRPC: RPC; PingRPC: RPC; TypingRPC: RPC; @@ -707,6 +721,37 @@ export type AuthAgent = { token: string; } & AgentToken; +export type AuthVisitor = { + msgId?: string; + msgType: "Auth.Visitor"; + widgetId: string; + token?: string; + visitorKey?: string; + localTimestamp?: string; + visitUrl?: string; +}; + +export type VisitorNew = { + msgId?: string; + msgType: "Visitor.New"; + widgetId: string; + localTimestamp: string; +}; + +export type VisitorVerifyEmail = { + msgId?: string; + msgType: "Visitor.VerifyEmail"; + email: string; +}; + +export type VisitorVerifyCode = { + msgId?: string; + msgType: "Visitor.VerifyCode"; + emailCode: string; +}; + +export type VisitorError = Error<"Visitor.Err">; + export type AuthError = Error<"Auth.Err">; export type AuthOk = { @@ -714,11 +759,26 @@ export type AuthOk = { msgType: "Auth.Ok"; sessionId: string; userId: string; + userName: string; + userEmail: string; helpdeskId: string; helpdesk?: Helpdesk; userAvatarUrl?: string; avatarLibraryUrl?: string; + visitorAvatarLibraryUrl?: string; role?: string; + widgetId?: string; + customerName?: string; + isVisitor?: boolean; + emailVerified?: boolean; + visitorToken?: string; +}; + +export type VisitorOk = { + msgId: string; + msgType: "Visitor.Ok"; + userId?: string; + token?: string; }; export type EchoGet = { @@ -898,6 +958,8 @@ export type EventRoom = { helpdeskId: string; id: string; name: string; + displayNameForUser: string | null; + displayNameForAgent: string | null; isTriage?: boolean; imageUrl: string; // search template email: string; // search template diff --git a/packages/fogbender-proto/src/useServerWs.tsx b/packages/fogbender-proto/src/useServerWs.tsx index f48fcc6e..679d2fde 100644 --- a/packages/fogbender-proto/src/useServerWs.tsx +++ b/packages/fogbender-proto/src/useServerWs.tsx @@ -6,7 +6,15 @@ import useWebSocket, { ReadyState, Options } from "react-use-websocket"; import { UNPARSABLE_JSON_OBJECT } from "react-use-websocket/src/lib/constants"; import { getServerApiUrl, getServerWsUrl } from "./config"; -import type { AnyToken, Helpdesk, FogSchema, PingPing, ServerCalls, ServerEvents } from "./schema"; +import type { + AnyToken, + Helpdesk, + FogSchema, + PingPing, + ServerCalls, + ServerEvents, + AuthVisitor, +} from "./schema"; import type { Client } from "./client"; type Requests = { @@ -34,7 +42,10 @@ const defaultOnError: NonNullable = (type, kind, ...errors) = }; const isAuthMessage = (message: FogSchema["outbound"]) => - message.msgType === "Auth.Agent" || message.msgType === "Auth.User"; + message.msgType === "Auth.Agent" || + message.msgType === "Auth.User" || + message.msgType === "Auth.Visitor" || + message.msgType === "Visitor.New"; export function useServerWs( client: Client, @@ -45,6 +56,9 @@ export function useServerWs( const [helpdesk, setHelpdesk] = React.useState(); const [avatarLibraryUrl, setAvatarLibraryUrl] = React.useState(); const [agentRole, setAgentRole] = React.useState(); + const [userType, setUserType] = React.useState< + "user" | "visitor-verified" | "visitor-unverified" + >(); const inFlight = React.useRef(new Map()); const queue = React.useRef([]); const ready = React.useRef(0); @@ -162,11 +176,78 @@ export function useServerWs( getWebSocket()?.close(); }; + const visitorTokenRef = React.useRef(); + React.useEffect(() => { onError("other", "other", ReadyState[readyState]); if (token && !authenticated.current && readyState === ReadyState.OPEN) { - if ("widgetId" in token) { + if ("widgetId" in token && "visitor" in token) { + serverCall({ + msgType: "Auth.Visitor", + widgetId: token.widgetId, + visitorKey: token.visitorKey, + token: visitorTokenRef.current || token.visitorToken, + localTimestamp: new Date().toLocaleString(), + visitUrl: token.visitUrl, + }).then( + r => { + if (r.msgType === "Auth.Ok") { + const { + sessionId, + userId, + userName, + userEmail, + helpdeskId, + userAvatarUrl, + customerName, + emailVerified, + visitorToken, + } = r; + if (visitorToken && visitorToken !== token.visitorToken) { + visitorTokenRef.current = visitorToken; + client.setVisitorInfo?.({ widgetId: token.widgetId, token: visitorToken, userId }); + } + authenticated.current = true; + setHelpdesk(r.helpdesk); + setAvatarLibraryUrl(r.visitorAvatarLibraryUrl); + if (emailVerified) { + setUserType("visitor-verified"); + } else { + setUserType("visitor-unverified"); + } + client.setSession?.({ + sessionId, + userId, + helpdeskId, + userAvatarUrl, + userName, + userEmail, + customerName, + }); + } else if (r.msgType === "Auth.Err") { + if (r.code === 401 || r.code === 403) { + onWrongToken(token); + } else { + onError("error", "other", new Error("Failed to authenticate " + JSON.stringify(r))); + } + } else if (r.msgType === "Error.Fatal") { + if ("code" in r && r.code === 409) { + onWrongToken(token); + } else { + onError( + "error", + "other", + new Error("Fatal error while authenticating " + JSON.stringify(r)) + ); + } + } + }, + r => { + onError("error", "other", r); + } + ); + } else if ("widgetId" in token && !("visitor" in token)) { const clone = { ...token }; clone.versions = { ...clone.versions, "fogbender-proto": "0.15.0" }; serverCall({ @@ -176,11 +257,28 @@ export function useServerWs( }).then( r => { if (r.msgType === "Auth.Ok") { - const { sessionId, userId, helpdeskId, userAvatarUrl } = r; + const { + sessionId, + userId, + userName, + userEmail, + helpdeskId, + userAvatarUrl, + customerName, + } = r; authenticated.current = true; setHelpdesk(r.helpdesk); + setUserType("user"); setAvatarLibraryUrl(r.avatarLibraryUrl); - client.setSession?.(sessionId, userId, helpdeskId, userAvatarUrl); + client.setSession?.({ + sessionId, + userId, + helpdeskId, + userAvatarUrl, + userName, + userEmail, + customerName, + }); } else if (r.msgType === "Auth.Err") { if (r.code === 401 || r.code === 403) { onWrongToken(token); @@ -234,7 +332,7 @@ export function useServerWs( authenticated.current = true; setHelpdesk(r.helpdesk); setAgentRole(r.role); - client.setSession?.(sessionId); + client.setSession?.({ sessionId }); } else if (r.msgType === "Auth.Err") { if (r.code === 401 || r.code === 403) { onWrongToken(token); @@ -355,6 +453,9 @@ export function useServerWs( isAgent: token && "agentId" in token, avatarLibraryUrl: avatarLibraryUrl, agentRole: agentRole, + userType, + widgetId: token && "widgetId" in token && token["widgetId"], + visitorJWT: visitorTokenRef.current, }; } diff --git a/packages/fogbender/src/checkToken.ts b/packages/fogbender/src/checkToken.ts index 6c3ecff7..4abe5943 100644 --- a/packages/fogbender/src/checkToken.ts +++ b/packages/fogbender/src/checkToken.ts @@ -1,10 +1,10 @@ -import { UserToken, Token } from "./types"; +import { UserToken, Token, VisitorToken } from "./types"; export function checkToken(token: Token | undefined) { if (token !== undefined) { const errors: { [key: string]: string } = {}; - ["userAvatarUrl", "widgetKey", "userJWT", "userHMAC", "userPaseto"].forEach( + ["userAvatarUrl", "widgetKey", "userJWT", "userHMAC", "userPaseto", "visitorKey"].forEach( x => typeof token[x] !== "string" && typeof token[x] !== "undefined" && @@ -23,6 +23,8 @@ export function checkToken(token: Token | undefined) { if (!(token.userJWT || token.userHMAC || token.userPaseto || token.widgetKey)) { errors.userJWT = "userJWT or widgetKey should be set"; } + } else if (isVisitorToken(token)) { + ["visitorKey"].forEach(isString); } else { ["widgetKey"].forEach(isString); } @@ -36,3 +38,7 @@ export function checkToken(token: Token | undefined) { export function isUserToken(token: Token | undefined): token is UserToken { return token ? "userId" in token : false; } + +export function isVisitorToken(token: Token | undefined): token is VisitorToken { + return token ? "visitor" in token : false; +} diff --git a/packages/fogbender/src/createIframe.ts b/packages/fogbender/src/createIframe.ts index 364792e0..6a7857f2 100644 --- a/packages/fogbender/src/createIframe.ts +++ b/packages/fogbender/src/createIframe.ts @@ -1,6 +1,7 @@ /* eslint-disable no-new */ import { ResizeSensor } from "css-element-queries"; import { Badge, Env, Token } from "."; +import { type VisitorInfo } from "./types"; type FogbenderEventMap = { "configured": boolean; @@ -48,11 +49,13 @@ export function renderIframe( token, headless, disableFit, + onVisitorInfo, }: { rootEl: HTMLElement; env: Env | undefined; url: string; token: Token; + onVisitorInfo: (info: VisitorInfo, reload: boolean) => void; headless?: boolean; disableFit?: boolean; }, @@ -88,6 +91,9 @@ export function renderIframe( window.Notification?.requestPermission().then(function (permission) { iFrame.contentWindow?.postMessage({ notificationsPermission: permission }, url); }); + } else if (e.data?.type === "VISITOR_INFO") { + const visitorInfo: VisitorInfo = JSON.parse(e.data.visitorInfo); + onVisitorInfo?.(visitorInfo, e.data.reload); } else if (e.data?.type === "BADGES" && e.data?.badges !== undefined) { const badges: FogbenderEventMap["fogbender.badges"]["badges"] = JSON.parse(e.data.badges); events.badges = badges; diff --git a/packages/fogbender/src/floatingWidget.tsx b/packages/fogbender/src/floatingWidget.tsx index 0ca9c871..4c5af970 100644 --- a/packages/fogbender/src/floatingWidget.tsx +++ b/packages/fogbender/src/floatingWidget.tsx @@ -239,40 +239,47 @@ function FloatingVerboseSvg() { function FloatingSvg() { return ( - + - - + + + - - - + + + diff --git a/packages/fogbender/src/index.ts b/packages/fogbender/src/index.ts index e7e0a465..04990033 100644 --- a/packages/fogbender/src/index.ts +++ b/packages/fogbender/src/index.ts @@ -1,19 +1,20 @@ -import { checkToken } from "./checkToken"; +import { checkToken, isVisitorToken } from "./checkToken"; import { createEvents, renderIframe } from "./createIframe"; import { createFloatingWidget } from "./floatingWidget"; import { renderUnreadBadge } from "./renderUnreadBadge"; -import type { Env, Fogbender, Token } from "./types"; +import type { Env, Fogbender, Token, VisitorInfo } from "./types"; export type { Env, Token, FallbackToken, + VisitorToken, UserToken, Badge, Fogbender, FogbenderLoader, Snapshot, } from "./types"; -export { checkToken, isUserToken } from "./checkToken"; +export { checkToken, isUserToken, isVisitorToken } from "./checkToken"; export const createNewFogbender = (): Fogbender => { const defaultUrl = "https://client.fogbender.com"; @@ -35,6 +36,17 @@ export const createNewFogbender = (): Fogbender => { } state.chatWindow?.focus(); }; + const storeVisitorInfo = (info: VisitorInfo) => { + if (state.token) { + state.token.visitorToken = info.token; + } + const { widgetId } = info; + try { + localStorage.setItem(`visitor-${widgetId}`, JSON.stringify(info)); + } catch (e) { + console.error(e); + } + }; const updateConfigured = () => { const configured = !!state.url && !!state.token; state.events.configured = configured; @@ -57,14 +69,31 @@ export const createNewFogbender = (): Fogbender => { }, async setToken(token) { const tokenCheck = checkToken(token); + // true means bad if (tokenCheck) { throw new Error("Wrong token format:\n" + JSON.stringify(tokenCheck, null, 1)); } state.token = token; + let visitorToken = undefined as undefined | string; + if (isVisitorToken(token)) { + const { widgetId } = token; + const key = `visitor-${widgetId}`; + try { + const visitorInfo = localStorage.getItem(key); + if (visitorInfo) { + const info = JSON.parse(visitorInfo); + visitorToken = info.token; + } + } catch (e) { + console.error(e); + } + } if (state.token) { state.token = { ...state.token, versions: { ...state.token.versions, ...state.versions, fogbender: "0.2.3" }, + visitorToken, + visitUrl: window?.parent?.location?.toString(), }; } updateConfigured(); @@ -88,27 +117,68 @@ export const createNewFogbender = (): Fogbender => { throw new Error("Fogbender: no token given"); } const { token, url, env } = state; - const cleanup = createFloatingWidget( + return createFloatingWidget( state, openWindow, - el => renderIframe(state, { rootEl: el, env, token, url, disableFit: true }, openWindow), + el => { + const rerender = () => { + return renderIframe( + state, + { + rootEl: el, + env, + token, + url, + disableFit: true, + onVisitorInfo: (info, reload) => { + storeVisitorInfo(info); + if (reload) { + cleanup(); + cleanup = rerender(); + } + }, + }, + openWindow + ); + }; + let cleanup = rerender(); + return () => { + cleanup(); + }; + }, opts ); - return cleanup; }, async renderIframe(opts) { - if (!state.url) { - throw new Error("Fogbender: no url given"); - } - if (!state.token) { - throw new Error("Fogbender: no token given"); - } - const cleanup = renderIframe( - state, - { ...opts, env: state.env, token: state.token, url: state.url }, - openWindow - ); - return cleanup; + const rerender = () => { + if (!state.url) { + throw new Error("Fogbender: no url given"); + } + if (!state.token) { + throw new Error("Fogbender: no token given"); + } + return renderIframe( + state, + { + ...opts, + env: state.env, + token: state.token, + url: state.url, + onVisitorInfo: (info, reload) => { + storeVisitorInfo(info); + if (reload) { + cleanup(); + cleanup = rerender(); + } + }, + }, + openWindow + ); + }; + let cleanup = rerender(); + return () => { + cleanup(); + }; }, async renderUnreadBadge(opts) { const cleanup = renderUnreadBadge(state, openWindow, opts); diff --git a/packages/fogbender/src/server.ts b/packages/fogbender/src/server.ts index 17c138ad..b6314eb3 100644 --- a/packages/fogbender/src/server.ts +++ b/packages/fogbender/src/server.ts @@ -1,7 +1,7 @@ import type { Env, Token, Badge, Fogbender, FogbenderLoader, Snapshot } from "./types"; export type { Env, Token, Badge, Fogbender, FogbenderLoader, Snapshot }; -export { checkToken, isUserToken } from "./checkToken"; +export { checkToken, isUserToken, isVisitorToken } from "./checkToken"; export const createNewFogbender = (): Fogbender => { const state = { diff --git a/packages/fogbender/src/types.ts b/packages/fogbender/src/types.ts index 22f2199f..d93f467f 100644 --- a/packages/fogbender/src/types.ts +++ b/packages/fogbender/src/types.ts @@ -2,6 +2,7 @@ export type Token = { widgetId: string; widgetKey?: string; + visitorKey?: string; customerId?: string; customerName?: string; userId?: string; @@ -12,6 +13,9 @@ export type Token = { userAvatarUrl?: string; userEmail?: string; versions?: { [key: string]: string }; + visitor?: true; + visitorToken?: string; + visitUrl?: string; }; // make sure to keep in sync with fogbender-ptoto schema @@ -30,10 +34,27 @@ export type UserToken = { versions?: { [key: string]: string }; }; +export type VisitorToken = { + widgetId: string; + visitor: true; + userId?: string; + visitorKey?: string; + visitorToken?: string; + visitUrl?: string; + versions?: { [key: string]: string }; +}; + +export type VisitorInfo = { + widgetId: string; + token: string; + userId: string; +}; + export type FallbackToken = { widgetId: string; widgetKey: string; versions?: { [key: string]: string }; + anonymous?: true; }; export type Env = "prod" | "staging" | "dev";