-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #51 from Rikthepixel/feature/user-can-start-a-conv…
…ersation Feature/user can start a conversation
- Loading branch information
Showing
102 changed files
with
2,803 additions
and
750 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import backend from '@/adapters/backend'; | ||
import { chatsResponseSchema } from '@/responses/messages/ChatsResponse'; | ||
import { getToken } from '@/lib/auth'; | ||
import { | ||
ChatResponse, | ||
chatResponseSchema, | ||
} from '@/responses/messages/ChatResponse'; | ||
import { | ||
ChatMessageResponse, | ||
chatMessageResponseSchema, | ||
} from '@/responses/messages/ChatMessageResponse'; | ||
import { chatCreatedResponse } from '@/responses/messages/ChatCreatedResponse'; | ||
|
||
export function startChat(userUid: string) { | ||
return backend | ||
.post('v1/chats', { json: { user: userUid } }) | ||
.then(async (res) => chatCreatedResponse.parse(await res.json())); | ||
} | ||
|
||
export async function getChats(signal?: AbortSignal) { | ||
return await backend | ||
.get('v1/chats', { signal }) | ||
.then(async (res) => chatsResponseSchema.parse(await res.json())); | ||
} | ||
|
||
export class ChatConnection { | ||
constructor( | ||
private target: EventTarget, | ||
private socket: WebSocket, | ||
) {} | ||
|
||
addEventListener( | ||
type: 'open', | ||
callback: (event: OpenedEvent) => void | null, | ||
options?: boolean | AddEventListenerOptions | undefined, | ||
): void; | ||
addEventListener( | ||
type: 'chat-message', | ||
callback: (event: ChatMessageEvent) => void | null, | ||
options?: boolean | AddEventListenerOptions | undefined, | ||
): void; | ||
addEventListener( | ||
type: 'chat-init', | ||
callback: (event: ChatInitEvent) => void | null, | ||
options?: boolean | AddEventListenerOptions | undefined, | ||
): void; | ||
addEventListener( | ||
type: 'close', | ||
callback: (event: ClosedEvent) => void | null, | ||
options?: boolean | AddEventListenerOptions | undefined, | ||
): void; | ||
addEventListener( | ||
type: string, | ||
callback: (event: any) => void | null, | ||
options?: boolean | AddEventListenerOptions | undefined, | ||
): void { | ||
this.target.addEventListener(type, callback, options); | ||
} | ||
|
||
removeEventListener( | ||
type: 'open' | 'close', | ||
callback: EventListenerOrEventListenerObject | null, | ||
options?: boolean | EventListenerOptions | undefined, | ||
): void { | ||
this.target.removeEventListener(type, callback, options); | ||
} | ||
|
||
sendMessage(content: string) { | ||
this.socket.send( | ||
JSON.stringify({ | ||
type: 'chat-message', | ||
content: content, | ||
}), | ||
); | ||
} | ||
|
||
disconnect() { | ||
this.socket.close(); | ||
} | ||
} | ||
|
||
class OpenedEvent extends Event { | ||
constructor() { | ||
super('open'); | ||
} | ||
} | ||
class ClosedEvent extends Event { | ||
constructor(public closedByClient: boolean) { | ||
super('close'); | ||
} | ||
} | ||
|
||
class ChatInitEvent extends Event { | ||
constructor(public data: ChatResponse) { | ||
super('chat-init'); | ||
} | ||
} | ||
|
||
class ChatMessageEvent extends Event { | ||
constructor(public data: ChatMessageResponse) { | ||
super('chat-message'); | ||
} | ||
} | ||
|
||
const events = { | ||
'chat-message': (data: unknown) => | ||
new ChatMessageEvent(chatMessageResponseSchema.parse(data)), | ||
'chat-init': (data: unknown) => | ||
new ChatInitEvent(chatResponseSchema.parse(data)), | ||
} as const; | ||
|
||
export async function connectToChat(uid: string, signal?: AbortSignal) { | ||
const token = await getToken(); | ||
if (!token) { | ||
throw new Error('User must be authenticated to connect to this chat'); | ||
} | ||
|
||
const url = new URL(import.meta.env.VITE_BACKEND_URL); | ||
url.pathname = `v1/chats/${uid}`; | ||
url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:'; | ||
url.searchParams.set('token', token); | ||
|
||
const socket = new WebSocket(url); | ||
const target = new EventTarget(); | ||
|
||
signal?.addEventListener('abort', function () { | ||
socket.close(); | ||
}); | ||
|
||
socket.onopen = function () { | ||
if (signal?.aborted) return socket.close(); | ||
target.dispatchEvent(new OpenedEvent()); | ||
}; | ||
|
||
socket.onmessage = function (e) { | ||
if (signal?.aborted) return socket.close(); | ||
const data = JSON.parse(e.data.toString()) as unknown; | ||
if ( | ||
typeof data !== 'object' || | ||
!data || | ||
!('type' in data) || | ||
typeof data.type !== 'string' | ||
) { | ||
return; | ||
} | ||
const event = Object.keys(events).includes(data.type) | ||
? events[data.type as keyof typeof events] | ||
: null; | ||
|
||
if (!event) return; | ||
target.dispatchEvent(event(data)); | ||
}; | ||
|
||
socket.onclose = function (e) { | ||
target.dispatchEvent(new ClosedEvent(e.code === 1000)); | ||
}; | ||
|
||
return new ChatConnection(target, socket); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import useAuth from "./stores/useAuth"; | ||
|
||
export const getToken = () => useAuth.getState().getToken(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,21 @@ | ||
import { User as Auth0User } from '@auth0/auth0-spa-js'; | ||
|
||
export default class AuthenticatedUser<TParent extends object> { | ||
constructor( | ||
public parent: TParent, | ||
public displayName: string, | ||
) {} | ||
|
||
public static fromAuth0(user: Auth0User) { | ||
return new AuthenticatedUser( | ||
user, | ||
user.name ?? user.nickname ?? 'unknown name', | ||
); | ||
} | ||
} | ||
import { User as Auth0User } from '@auth0/auth0-spa-js'; | ||
|
||
export default class AuthenticatedUser<TParent extends object> { | ||
constructor( | ||
public parent: TParent, | ||
public username: string, | ||
public providerId: string, | ||
) {} | ||
|
||
public static fromAuth0(user: Auth0User) { | ||
if (!user.sub) { | ||
throw new Error('Malformed authenticated user'); | ||
} | ||
|
||
return new AuthenticatedUser( | ||
user, | ||
user.name ?? user.nickname ?? 'unknown name', | ||
user.sub, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.