Skip to content

Commit

Permalink
Merge pull request #18 from stagas/webrtc
Browse files Browse the repository at this point in the history
feat: webrtc
  • Loading branch information
stagas authored Oct 8, 2024
2 parents be86a39 + 7b79bb4 commit c470a20
Show file tree
Hide file tree
Showing 23 changed files with 386 additions and 32 deletions.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@
- [x] WebAudio
- [x] Wasm AudioWorklet
- [ ] WebGL
- [ ] WebRTC
- [x] WebRTC
- [x] QRCode
- [ ] Maps
- [x] Testing
- [x] Coverage
- [x] Unit
Expand All @@ -78,11 +79,24 @@
- [x] Users
- [x] Sessions
- [x] Pages
- [x] App
- [x] Chat
- [x] Channels
- [x] Chat
- [x] Messages
- [x] Users
- [x] About
- [x] App
- [x] AssemblyScript
- [x] Canvas
- [x] Home
- [x] OAuthRegister
- [x] QrCode
- [x] WebSockets
- [ ] UI Showcase
- [x] Components
- [x] Header
- [x] Toast
- [x] Catch/show errors
- [x] Login
- [x] Logout
- [x] OAuthLogin
Expand All @@ -94,8 +108,6 @@
- [x] Input
- [x] Label
- [x] Link
- [x] Toast
- [x] Catch/show errors

## Setup

Expand Down
20 changes: 19 additions & 1 deletion api/chat/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// deno-lint-ignore-file require-await
import { UserSession } from '~/api/auth/types.ts'
import { bus } from "~/api/chat/bus.ts"
import { broadcast, subs } from "~/api/chat/routes.ts"
import { ChatChannel, ChatMessage, ChatMessageType, UiChannel } from "~/api/chat/types.ts"
import { ChatChannel, ChatDirectMessage, ChatDirectMessageType, ChatMessage, ChatMessageType, UiChannel } from "~/api/chat/types.ts"
import { createBus } from '~/api/core/create-bus.ts'
import { Context } from '~/api/core/router.ts'
import { getSession } from '~/api/core/sessions.ts'
Expand Down Expand Up @@ -139,6 +140,23 @@ export async function sendMessageToChannel(ctx: Context, type: ChatMessageType,
return message
}

actions.post.sendMessageToUser = sendMessageToUser
export async function sendMessageToUser(ctx: Context, type: ChatDirectMessageType, targetNick: string, text: string = '') {
const { nick } = getSession(ctx)
if (nick === targetNick) return

const msg: ChatDirectMessage = {
type,
nick,
text
}

const sub = subs.get(targetNick)
if (sub) {
sub.send(msg)
}
}

actions.post.joinChannel = joinChannel
export async function joinChannel(ctx: Context, channel: string) {
const session = getSession(ctx)
Expand Down
12 changes: 12 additions & 0 deletions api/chat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@ export interface ChatMessage {
nick: string
text: string
}

export type ChatDirectMessageType =
| 'directMessage'
| 'webrtc:offer'
| 'webrtc:answer'
| 'webrtc:end'

export interface ChatDirectMessage {
type: ChatDirectMessageType
nick: string
text: string
}
4 changes: 2 additions & 2 deletions api/ws/routes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createBus } from '~/api/core/create-bus.ts'
import { Router } from '~/api/core/router.ts'
import type { Router } from '~/api/core/router.ts'
import { getSession } from '~/api/core/sessions.ts'

const clients = new Set<WebSocket>()
Expand All @@ -26,7 +26,7 @@ export function mount(app: Router) {

ctx.log('[ws] connecting...', nick)

const { socket: ws, response } = Deno.upgradeWebSocket(ctx.request, {
const { response, socket: ws } = Deno.upgradeWebSocket(ctx.request, {
idleTimeout: 0
})

Expand Down
7 changes: 3 additions & 4 deletions src/comp/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Sigui } from 'sigui'
import { UserForgot, UserLogin } from '~/api/auth/types.ts'
import * as actions from '~/src/rpc/auth.ts'
import { Link } from '~/src/ui/Link.tsx'
import { Fieldset, Input, Label } from '~/src/ui/index.ts'
import { Button, Fieldset, Input, Label, Link } from '~/src/ui/index.ts'
import { parseForm } from '~/src/util/parse-form.ts'

export function Login() {
Expand Down Expand Up @@ -61,7 +60,7 @@ export function Login() {

<div class="flex flex-row items-center justify-end gap-2">
<Link onclick={() => info.mode = 'forgot'}>Forgot password</Link>
<button type="submit">Login</button>
<Button type="submit">Login</Button>
</div>

<span>{() => info.error}</span>
Expand Down Expand Up @@ -89,7 +88,7 @@ export function Login() {

<div class="flex flex-row items-center justify-end gap-2">
<span><Link onclick={() => info.mode = 'login'}>Login using password</Link></span>
<button type="submit">Send reset link</button>
<Button type="submit">Send reset link</Button>
</div>

<span>{() => info.error}</span>
Expand Down
5 changes: 3 additions & 2 deletions src/comp/Logout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { logout } from '~/src/rpc/auth.ts'
import { state } from '~/src/state.ts'
import { Button } from '~/src/ui/index.ts'

export function Logout({ then }: { then?: () => void }) {
return <button onclick={() =>
return <Button onclick={() =>
logout()
.then(() => state.user = null)
.then(() => then?.())
}>Logout</button>
}>Logout</Button>
}
5 changes: 4 additions & 1 deletion src/comp/OAuthLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { on } from 'utils'
import { whoami } from '~/src/rpc/auth.ts'
import { state } from '~/src/state.ts'
import { Button } from '~/src/ui/index.ts'

export function OAuthLogin() {
function oauthLogin(provider: string) {
Expand Down Expand Up @@ -30,5 +31,7 @@ export function OAuthLogin() {
}, { once: true })
}

return <button onclick={() => oauthLogin('github')}>Proceed with GitHub</button>
return <Button onclick={() => oauthLogin('github')}>
Proceed with GitHub
</Button>
}
4 changes: 2 additions & 2 deletions src/comp/Register.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Sigui } from 'sigui'
import { UserRegister } from '~/api/auth/types.ts'
import * as actions from '~/src/rpc/auth.ts'
import { Fieldset, Input, Label } from '~/src/ui/index.ts'
import { Button, Fieldset, Input, Label } from '~/src/ui/index.ts'
import { parseForm } from '~/src/util/parse-form.ts'

export function Register() {
Expand Down Expand Up @@ -50,7 +50,7 @@ export function Register() {
</Label>

<div class="flex flex-row items-center justify-end gap-2">
<button type="submit">Register</button>
<Button type="submit">Register</Button>
</div>

<span>{() => info.error}</span>
Expand Down
4 changes: 3 additions & 1 deletion src/pages/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dispose, Sigui } from 'sigui'
import { Sigui } from 'sigui'
import { dom } from 'utils'
import { CachingRouter } from '~/lib/caching-router.ts'
import { Header } from '~/src/comp/Header.tsx'
Expand All @@ -13,6 +13,7 @@ import { Chat } from '~/src/pages/Chat/Chat.tsx'
import { Home } from '~/src/pages/Home.tsx'
import { OAuthRegister } from '~/src/pages/OAuthRegister.tsx'
import { QrCode } from '~/src/pages/QrCode.tsx'
import { UiShowcase } from '~/src/pages/UiShowcase.tsx'
import { WebSockets } from '~/src/pages/WebSockets.tsx'
import { whoami } from '~/src/rpc/auth.ts'
import { state } from '~/src/state.ts'
Expand All @@ -31,6 +32,7 @@ export function App() {

const router = CachingRouter({
'/': () => <Home />,
'/ui': () => <UiShowcase />,
'/chat': () => <Chat />,
'!/ws': () => <WebSockets />,
'!/canvas': () => <Canvas width={info.$.canvasWidth} height={info.$.canvasHeight} />,
Expand Down
5 changes: 3 additions & 2 deletions src/pages/AssemblyScript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Sigui } from 'sigui'
import { Player } from '~/src/as/pkg/player.ts'
import { PkgService } from '~/src/as/pkg/service.ts'
import pkg from '~/src/as/pkg/wasm.ts'
import { Button } from '~/src/ui/index.ts'

let audioContext: AudioContext

Expand Down Expand Up @@ -34,7 +35,7 @@ export function AssemblyScript() {
<br />
Worker: {() => info.fromWorker}
<br />
<button onclick={() => pkgPlayer.play()}>Play</button>
<button onclick={() => pkgPlayer.stop()}>Stop</button>
<Button onclick={() => pkgPlayer.play()}>Play</Button>
<Button onclick={() => pkgPlayer.stop()}>Stop</Button>
</div>
}
5 changes: 3 additions & 2 deletions src/pages/Chat/Channels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { icon } from '~/lib/icon.ts'
import * as actions from '~/src/rpc/chat.ts'
import { state } from '~/src/state.ts'
import { byName, hasChannel } from './util.ts'
import { H3 } from '~/src/ui/Heading.tsx'

export function Channels({ overlay = true }: { overlay?: boolean }) {
using $ = Sigui()
return <div class={cn(
"w-[30%] max-w-56 flex flex-col gap-2 pt-1.5 pb-2.5 pr-4 mr-4 flex-shrink-0 border-r border-r-neutral-700",
{ 'absolute bg-neutral-900 h-[calc(100vh-4.5rem)]': overlay },
)}>
<h3 class="min-h-9 flex items-center justify-between border-b border-neutral-600">
<H3>
<span>Channels</span>
<button class="flex items-center text-sm pr-2 gap-1"
onclick={async () => {
Expand Down Expand Up @@ -47,7 +48,7 @@ export function Channels({ overlay = true }: { overlay?: boolean }) {
>
{icon(Plus, { size: 16, 'stroke-width': 1.5 })}
</button>
</h3>
</H3>

<div class="flex flex-col overflow-y-scroll leading-[19px]">
{() => state.channelsList.map(channel => {
Expand Down
64 changes: 60 additions & 4 deletions src/pages/Chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { Sigui } from 'sigui'
import type { ChatMessage } from '~/api/chat/types.ts'
import { dispose, Sigui } from 'sigui'
import type { ChatDirectMessage, ChatMessage } from '~/api/chat/types.ts'
import { Channels } from '~/src/pages/Chat/Channels.tsx'
import { Messages } from '~/src/pages/Chat/Messages.tsx'
import { Users } from '~/src/pages/Chat/Users.tsx'
import { byName, byNick, hasChannel } from '~/src/pages/Chat/util.ts'
import { VideoCall } from '~/src/pages/Chat/VideoCall.tsx'
import * as actions from '~/src/rpc/chat.ts'
import { screen } from '~/src/screen.ts'
import { state } from '~/src/state.ts'
import { go } from '~/src/ui/Link.tsx'

export interface RemoteSdp {
type: 'webrtc:offer' | 'webrtc:answer'
nick: string
text: string
}

export function Chat() {
using $ = Sigui()

const info = $({
started: null as null | true,

showChannelsOverlay: false,

videoCallType: null as null | 'offer' | 'answer',
videoCallTargetNick: null as null | string,
remoteSdp: null as null | RemoteSdp,
})

actions.listChannels().then(channels => {
Expand All @@ -31,7 +43,7 @@ export function Chat() {
})

chat.onmessage = ({ data }) => {
const msg = JSON.parse(data) as ChatMessage
const msg = JSON.parse(data) as ChatMessage | ChatDirectMessage

switch (msg.type) {
case 'started':
Expand Down Expand Up @@ -59,6 +71,29 @@ export function Chat() {
if (channel.users.find(u => u.nick === msg.nick)) return
channel.users = [...channel.users, { nick: msg.nick }].sort(byNick)
}
break
}

case 'directMessage': {
alert(msg.text)
break
}

case 'webrtc:offer': {
info.remoteSdp = msg as RemoteSdp
info.videoCallType = 'answer'
info.videoCallTargetNick = msg.nick
break
}

case 'webrtc:answer': {
info.remoteSdp = msg as RemoteSdp
break
}

case 'webrtc:end': {
info.videoCallTargetNick = null
break
}
}
}
Expand All @@ -68,6 +103,14 @@ export function Chat() {
}
})

$.fx(() => {
const { videoCallTargetNick } = $.of(info)
$()
return () => {
actions.sendMessageToUser('webrtc:end', videoCallTargetNick)
}
})

$.fx(() => {
const { started } = $.of(info)
const { channelsList, currentChannelName } = state
Expand Down Expand Up @@ -112,6 +155,19 @@ export function Chat() {
: <div />
}
<Messages showChannelsOverlay={info.$.showChannelsOverlay} />
{() => screen.md ? <Users /> : <div />}
{() => screen.md ? <Users onUserClick={nick => {
info.videoCallType = 'offer'
info.videoCallTargetNick = nick
}} /> : <div />}

{() => dispose() && info.videoCallType && info.videoCallTargetNick
?
<VideoCall
type={info.$.videoCallType}
targetNick={info.$.videoCallTargetNick}
remoteSdp={info.$.remoteSdp} />
:
<div />
}
</div>
}
5 changes: 3 additions & 2 deletions src/pages/Chat/Messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { colorizeNick } from '~/src/pages/Chat/util.ts'
import * as actions from '~/src/rpc/chat.ts'
import { screen } from '~/src/screen.ts'
import { state } from '~/src/state.ts'
import { H3 } from '~/src/ui/Heading.tsx'

export function Messages({ showChannelsOverlay }: { showChannelsOverlay: Signal<boolean> }) {
using $ = Sigui()
Expand Down Expand Up @@ -57,7 +58,7 @@ export function Messages({ showChannelsOverlay }: { showChannelsOverlay: Signal<
'w-full pt-1.5 pb-2.5 flex flex-col max-h-[calc(100vh-4rem)]',
{ 'w-[70%]': screen.md },
)} onclick={() => info.showChannelsOverlay = false}>
<h3 class="min-h-9 flex items-center border-b border-neutral-600 justify-between">
<H3>
<div class="flex flex-row">
{() => screen.sm
?
Expand All @@ -77,7 +78,7 @@ export function Messages({ showChannelsOverlay }: { showChannelsOverlay: Signal<
<button class="flex items-center text-sm pr-2 gap-1" title="Leave channel">
{icon(LogOut, { size: 16, 'stroke-width': 1.5 })}
</button>
</h3>
</H3>

<div ref="chatMessages" class="overflow-y-scroll leading-[19px]">
<div class="flex flex-col justify-end min-h-[calc(100vh-8.75rem)]">
Expand Down
Loading

0 comments on commit c470a20

Please sign in to comment.