Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: webrtc #18

Merged
merged 2 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading