Skip to content

Commit

Permalink
Visitor (anon user) support
Browse files Browse the repository at this point in the history
  • Loading branch information
abs authored and JLarky committed Oct 21, 2023
1 parent bb6b077 commit c1c5dd3
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 64 deletions.
6 changes: 6 additions & 0 deletions .changeset/shaggy-eyes-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"fogbender": minor
"fogbender-proto": minor
---

Visitor (anon user) support
28 changes: 22 additions & 6 deletions packages/fogbender-proto/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
26 changes: 14 additions & 12 deletions packages/fogbender-proto/src/context/sharedRoster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } =
Expand Down Expand Up @@ -120,24 +121,24 @@ 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,
}).then(x => {
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<StreamGet>({
msgType: "Stream.Get",
topic,
Expand All @@ -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;
Expand Down Expand Up @@ -276,17 +277,17 @@ 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,
}).then(x => {
console.assert(x.msgType === "Stream.SubOk");
});
}
}, [fogSessionId, userId, serverCall]);
}, [fogSessionId, ourId, serverCall]);

const updateCustomers = React.useCallback((customersIn: EventCustomer[]) => {
setCustomers(customers => {
Expand Down Expand Up @@ -387,6 +388,7 @@ export const useSharedRosterInternal = ({
rosterViewSectionsAtom,
rosterSectionsActionsAtom,
rosterRoomFamily,
ourId,
};
}, [roster, roomById, badges, customers]);
};
29 changes: 27 additions & 2 deletions packages/fogbender-proto/src/context/ws.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type Author = {
id: string;
name: string;
type: AuthorType;
userType?: "visitor-verified" | "visitor-unverified" | "user";
avatarUrl?: string;
};

Expand Down Expand Up @@ -122,7 +123,15 @@ function useProviderValue(
const [userAvatarUrl, setUserAvatarUrl] = React.useState<string>();
const [providerClient] = React.useState<Client>(() => ({
...client,
setSession(sessionId, userId, helpdeskId, userAvatarUrl) {
setSession({
sessionId,
userId,
helpdeskId,
userAvatarUrl,
userName,
userEmail,
customerName,
}) {
setFogSessionId(sessionId);
if (userId) {
setUserId(userId);
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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,
Expand All @@ -182,6 +201,7 @@ function useProviderValue(
isAuthenticated: ws.isAuthenticated,
isTokenWrong: ws.isTokenWrong,
isAgent: ws.isAgent,
userType: ws.userType,
avatarLibraryUrl: ws.avatarLibraryUrl,
token,
fogSessionId,
Expand All @@ -190,8 +210,11 @@ function useProviderValue(
workspaceId,
userAvatarUrl,
agentRole: ws.agentRole,
visitorJWT: ws.visitorJWT,
};
}, [
client,
ws.widgetId,
ws.serverCall,
ws.lastIncomingMessageAtom,
sharedRosterAtom,
Expand All @@ -201,7 +224,9 @@ function useProviderValue(
ws.isAuthenticated,
ws.isTokenWrong,
ws.isAgent,
ws.userType,
ws.avatarLibraryUrl,
ws.visitorJWT,
token,
fogSessionId,
userId,
Expand Down
64 changes: 63 additions & 1 deletion packages/fogbender-proto/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -75,6 +85,10 @@ export type APISchema = {
MessageRefreshFilesRPC: RPC<MessageRefreshFiles, MessageOk>;
AuthUserRPC: RPC<AuthUser, AuthError | AuthOk>;
AuthAgentRPC: RPC<AuthAgent, AuthError | AuthOk>;
AuthVisitorRPC: RPC<AuthVisitor, AuthError | AuthOk>;
VisitorNewRPC: RPC<VisitorNew, VisitorError | VisitorOk>;
VisitorVerifyEmailRPC: RPC<VisitorVerifyEmail, VisitorError | VisitorOk>;
VisitorVerifyCodeRPC: RPC<VisitorVerifyCode, VisitorError | VisitorOk>;
EchoRPC: RPC<EchoGet, EchoOk>;
PingRPC: RPC<PingPing, PingPong>;
TypingRPC: RPC<TypingSet, undefined>;
Expand Down Expand Up @@ -707,18 +721,64 @@ 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 = {
msgId: string;
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 = {
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit c1c5dd3

Please sign in to comment.