add presence and user list
This commit is contained in:
parent
f9a62cec4e
commit
e31d8c5973
10 changed files with 263 additions and 87 deletions
|
@ -278,6 +278,79 @@ body {
|
||||||
font-size: 0.833rem;
|
font-size: 0.833rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* sidebar */
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--background-color-0);
|
||||||
|
height: 100%;
|
||||||
|
min-width: 255px;
|
||||||
|
max-width: 255px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.sidebar-container {
|
||||||
|
flex-basis: 100%;
|
||||||
|
min-width: unset;
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: var(--space-xs);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: left;
|
||||||
|
border: none;
|
||||||
|
background-color: var(--background-color-0);
|
||||||
|
padding: var(--space-xs);
|
||||||
|
margin-bottom: var(--space-xxs);
|
||||||
|
color: currentColor;
|
||||||
|
font: inherit;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
width: 100%;
|
||||||
|
max-height: 3.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button span {
|
||||||
|
margin-left: var(--space-xxs);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button div {
|
||||||
|
display: inline;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
/* TODO: HACK! */
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button .icon-button {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button.selected .icon-button,
|
||||||
|
.sidebar-button:hover .icon-button {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button.selected,
|
||||||
|
.sidebar-button:hover {
|
||||||
|
background-color: var(--background-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
/*! the tweaks below are heavily based on modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
/*! the tweaks below are heavily based on modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
||||||
|
|
||||||
*,
|
*,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { HashIcon, MenuIcon } from "svelte-feather-icons";
|
import { HashIcon, MenuIcon, UsersIcon } from "svelte-feather-icons";
|
||||||
import { getItem } from "../storage";
|
import { getItem } from "../storage";
|
||||||
import { overlayStore, showSidebar } from "../stores";
|
import { overlayStore, showPresenceSidebar, showSidebar } from "../stores";
|
||||||
|
|
||||||
export let channel;
|
export let channel;
|
||||||
</script>
|
</script>
|
||||||
|
@ -10,6 +10,11 @@
|
||||||
.menu-button {
|
.menu-button {
|
||||||
margin-right: var(--space-md);
|
margin-right: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-buttons {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: var(--space-xs);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
|
@ -20,4 +25,9 @@
|
||||||
{/if}
|
{/if}
|
||||||
<HashIcon />
|
<HashIcon />
|
||||||
<span class="h5 top-bar-heading" on:click="{ () => overlayStore.open('editChannel', {channel}) }">{ channel.name }</span>
|
<span class="h5 top-bar-heading" on:click="{ () => overlayStore.open('editChannel', {channel}) }">{ channel.name }</span>
|
||||||
|
<div class="right-buttons">
|
||||||
|
<button class="icon-button" on:click="{ () => showPresenceSidebar.set(!showPresenceSidebar.value) }" aria-label="Toggle user list">
|
||||||
|
<UsersIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { CloudIcon } from "svelte-feather-icons";
|
import { CloudIcon } from "svelte-feather-icons";
|
||||||
import { gatewayStatus, showSidebar, selectedChannel, smallViewport, showChannelView, theme } from "../stores";
|
import { gatewayStatus, showSidebar, selectedChannel, smallViewport, showChannelView, theme, showPresenceSidebar } from "../stores";
|
||||||
import ChannelView from "./ChannelView.svelte";
|
import ChannelView from "./ChannelView.svelte";
|
||||||
import OverlayProvider from "./overlays/OverlayProvider.svelte";
|
import OverlayProvider from "./overlays/OverlayProvider.svelte";
|
||||||
|
import PresenceSidebar from "./PresenceSidebar.svelte";
|
||||||
import Sidebar from "./Sidebar.svelte";
|
import Sidebar from "./Sidebar.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -48,7 +49,10 @@
|
||||||
{#if $showSidebar || $selectedChannel.id === -1}
|
{#if $showSidebar || $selectedChannel.id === -1}
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
{/if}
|
{/if}
|
||||||
{#if !($smallViewport && $showSidebar) && $showChannelView && $selectedChannel.id !== -1}
|
{#if !($smallViewport && $showSidebar) && !($smallViewport && $showPresenceSidebar) && $showChannelView && $selectedChannel.id !== -1}
|
||||||
<ChannelView channel={$selectedChannel} />
|
<ChannelView channel={$selectedChannel} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $showPresenceSidebar}
|
||||||
|
<PresenceSidebar />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
47
frontend/src/components/PresenceSidebar.svelte
Normal file
47
frontend/src/components/PresenceSidebar.svelte
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<script>
|
||||||
|
import { ArrowLeftIcon, AtSignIcon } from "svelte-feather-icons";
|
||||||
|
import { quadInOut } from "svelte/easing";
|
||||||
|
import { maybeFly } from "../animations";
|
||||||
|
import { presenceStore, showChannelView, showPresenceSidebar, smallViewport } from "../stores";
|
||||||
|
|
||||||
|
let pendingExit = false;
|
||||||
|
|
||||||
|
const scheduleClose = () => {
|
||||||
|
if ($smallViewport) {
|
||||||
|
$showChannelView = false;
|
||||||
|
pendingExit = true;
|
||||||
|
}
|
||||||
|
$showPresenceSidebar = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const outroEnd = () => {
|
||||||
|
if (pendingExit) {
|
||||||
|
pendingExit = false;
|
||||||
|
$showChannelView = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="sidebar-container" transition:maybeFly="{{ duration: 200, easing: quadInOut, x: 10 }}" on:outroend="{ outroEnd }">
|
||||||
|
<div class="top-bar">
|
||||||
|
<span class="input-label">User List</span>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar">
|
||||||
|
{#each $presenceStore as entry}
|
||||||
|
<button class="sidebar-button">
|
||||||
|
<div>
|
||||||
|
<AtSignIcon />
|
||||||
|
</div>
|
||||||
|
<span>{ entry.user.username }</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{#if $smallViewport}
|
||||||
|
<button on:click={ scheduleClose } class="sidebar-button">
|
||||||
|
<div>
|
||||||
|
<ArrowLeftIcon />
|
||||||
|
</div>
|
||||||
|
<span>Back</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -78,74 +78,4 @@
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: var(--background-color-0);
|
|
||||||
height: 100%;
|
|
||||||
min-width: 255px;
|
|
||||||
max-width: 255px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.sidebar-container {
|
|
||||||
flex-basis: 100%;
|
|
||||||
min-width: unset;
|
|
||||||
max-width: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: var(--space-xs);
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left;
|
|
||||||
border: none;
|
|
||||||
background-color: var(--background-color-0);
|
|
||||||
padding: var(--space-xs);
|
|
||||||
margin-bottom: var(--space-xxs);
|
|
||||||
color: currentColor;
|
|
||||||
font: inherit;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
width: 100%;
|
|
||||||
max-height: 3.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button span {
|
|
||||||
margin-left: var(--space-xxs);
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button div {
|
|
||||||
display: inline;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
/* TODO: HACK! */
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button .icon-button {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button.selected .icon-button,
|
|
||||||
.sidebar-button:hover .icon-button {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button.selected,
|
|
||||||
.sidebar-button:hover {
|
|
||||||
background-color: var(--background-color-2);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -27,6 +27,8 @@ export const GatewayPayloadType = {
|
||||||
MessageDelete: 122,
|
MessageDelete: 122,
|
||||||
|
|
||||||
TypingStart: 130,
|
TypingStart: 130,
|
||||||
|
|
||||||
|
PresenceUpdate: 140
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GatewayEventType = {
|
export const GatewayEventType = {
|
||||||
|
@ -37,6 +39,11 @@ export const GatewayEventType = {
|
||||||
BadAuth: -3,
|
BadAuth: -3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GatewayPresenceStatus = {
|
||||||
|
Offline: 0,
|
||||||
|
Online: 1
|
||||||
|
}
|
||||||
|
|
||||||
const log = logger("Gateway");
|
const log = logger("Gateway");
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import gateway, { GatewayEventType, GatewayPayloadType } from "./gateway";
|
import gateway, { GatewayEventType, GatewayPayloadType, GatewayPresenceStatus } from "./gateway";
|
||||||
import logger from "./logging";
|
import logger from "./logging";
|
||||||
import request from "./request";
|
import request from "./request";
|
||||||
import { apiRoute, getItem, setItem } from "./storage";
|
import { apiRoute, getItem, setItem } from "./storage";
|
||||||
|
@ -398,8 +398,45 @@ class TypingStore extends Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PresenceStore extends Store {
|
||||||
|
constructor() {
|
||||||
|
super([], "PresenceStore");
|
||||||
|
|
||||||
|
gateway.subscribe(GatewayEventType.Ready, ({ presence }) => {
|
||||||
|
this.ingestPresenceUpdate(presence);
|
||||||
|
});
|
||||||
|
|
||||||
|
gateway.subscribe(GatewayEventType.PresenceUpdate, (data) => {
|
||||||
|
this.ingestPresenceUpdate(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
entryIndexByUserId(userId) {
|
||||||
|
return this.value.findIndex(a => a.user.id === userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ingestPresenceUpdate(payload) {
|
||||||
|
payload.forEach((entry) => {
|
||||||
|
const existingEntry = this.entryIndexByUserId(entry.user.id);
|
||||||
|
if (existingEntry !== -1 && entry.status === GatewayPresenceStatus.Offline) {
|
||||||
|
this.value.splice(existingEntry, 1);
|
||||||
|
} else if (existingEntry !== -1 && entry.status !== GatewayPresenceStatus.Offline) {
|
||||||
|
this.value[existingEntry] = entry;
|
||||||
|
} else {
|
||||||
|
// don't need to push the status, since we remove offline members from the presence list
|
||||||
|
this.value.push({
|
||||||
|
user: entry.user
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(this.value);
|
||||||
|
this.updated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const selectedChannel = new Store({ id: -1, name: "none", creator_id: -1 }, "selectedChannel");
|
export const selectedChannel = new Store({ id: -1, name: "none", creator_id: -1 }, "selectedChannel");
|
||||||
export const showSidebar = new Store(true, "showSidebar");
|
export const showSidebar = new Store(true, "showSidebar");
|
||||||
|
export const showPresenceSidebar = new Store(false, "showPresenceSidebar");
|
||||||
export const smallViewport = new Store(false, "smallViewport");
|
export const smallViewport = new Store(false, "smallViewport");
|
||||||
export const showChannelView = new Store(true, "showChannelView");
|
export const showChannelView = new Store(true, "showChannelView");
|
||||||
export const theme = new StorageItemStore("ui:theme");
|
export const theme = new StorageItemStore("ui:theme");
|
||||||
|
@ -410,10 +447,12 @@ export const messagesStoreProvider = new MessagesStoreProvider();
|
||||||
export const userInfoStore = new UserInfoStore();
|
export const userInfoStore = new UserInfoStore();
|
||||||
export const overlayStore = new OverlayStore();
|
export const overlayStore = new OverlayStore();
|
||||||
export const typingStore = new TypingStore();
|
export const typingStore = new TypingStore();
|
||||||
|
export const presenceStore = new PresenceStore();
|
||||||
|
|
||||||
export const allStores = {
|
export const allStores = {
|
||||||
selectedChannel,
|
selectedChannel,
|
||||||
showSidebar,
|
showSidebar,
|
||||||
|
showPresenceSidebar,
|
||||||
showChannelView,
|
showChannelView,
|
||||||
smallViewport,
|
smallViewport,
|
||||||
theme,
|
theme,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export enum GatewayPayloadType {
|
export enum GatewayPayloadType {
|
||||||
Hello = 0,
|
Hello = 0,
|
||||||
Authenticate,
|
Authenticate, // client
|
||||||
Ready,
|
Ready,
|
||||||
Ping,
|
Ping, // client
|
||||||
|
|
||||||
ChannelCreate = 110,
|
ChannelCreate = 110,
|
||||||
ChannelUpdate,
|
ChannelUpdate,
|
||||||
|
@ -13,4 +13,11 @@ export enum GatewayPayloadType {
|
||||||
MessageDelete,
|
MessageDelete,
|
||||||
|
|
||||||
TypingStart = 130,
|
TypingStart = 130,
|
||||||
|
|
||||||
|
PresenceUpdate = 140,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum GatewayPresenceStatus {
|
||||||
|
Offline = 0,
|
||||||
|
Online,
|
||||||
}
|
}
|
||||||
|
|
9
src/gateway/gatewaypresence.ts
Normal file
9
src/gateway/gatewaypresence.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { GatewayPresenceStatus } from "./gatewaypayloadtype"
|
||||||
|
|
||||||
|
export interface GatewayPresenceEntry {
|
||||||
|
user: {
|
||||||
|
username: string,
|
||||||
|
id: number
|
||||||
|
},
|
||||||
|
status: GatewayPresenceStatus
|
||||||
|
}
|
|
@ -5,7 +5,8 @@ import { decodeTokenOrNull, getPublicUserObject } from "../auth";
|
||||||
import { query } from "../database";
|
import { query } from "../database";
|
||||||
import { gatewayErrors } from "../errors";
|
import { gatewayErrors } from "../errors";
|
||||||
import { GatewayPayload } from "../types/gatewaypayload";
|
import { GatewayPayload } from "../types/gatewaypayload";
|
||||||
import { GatewayPayloadType } from "./gatewaypayloadtype";
|
import { GatewayPayloadType, GatewayPresenceStatus } from "./gatewaypayloadtype";
|
||||||
|
import { GatewayPresenceEntry } from "./gatewaypresence";
|
||||||
|
|
||||||
const GATEWAY_BATCH_INTERVAL = 50000;
|
const GATEWAY_BATCH_INTERVAL = 50000;
|
||||||
const GATEWAY_PING_INTERVAL = 40000;
|
const GATEWAY_PING_INTERVAL = 40000;
|
||||||
|
@ -16,7 +17,7 @@ const MAX_GATEWAY_SESSIONS_PER_USER = 5;
|
||||||
const dispatchChannels = new Map<string, Set<WebSocket>>();
|
const dispatchChannels = new Map<string, Set<WebSocket>>();
|
||||||
|
|
||||||
// mapping between a user id and the websocket sessions it has
|
// mapping between a user id and the websocket sessions it has
|
||||||
const sessionsByUserId = new Map<number, Set<WebSocket>>();
|
const sessionsByUserId = new Map<number, WebSocket[]>();
|
||||||
|
|
||||||
function clientSubscribe(ws: WebSocket, dispatchChannel: string) {
|
function clientSubscribe(ws: WebSocket, dispatchChannel: string) {
|
||||||
ws.state.dispatchChannels.add(dispatchChannel);
|
ws.state.dispatchChannels.add(dispatchChannel);
|
||||||
|
@ -71,7 +72,9 @@ export function dispatch(channel: string, message: GatewayPayload) {
|
||||||
if (!members) return;
|
if (!members) return;
|
||||||
|
|
||||||
members.forEach(e => {
|
members.forEach(e => {
|
||||||
|
if (e.state.ready) {
|
||||||
e.send(JSON.stringify(message));
|
e.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +123,35 @@ function sendPayload(ws: WebSocket, payload: GatewayPayload) {
|
||||||
ws.send(JSON.stringify(payload));
|
ws.send(JSON.stringify(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPresenceEntryForUser(user: User, status: GatewayPresenceStatus): GatewayPresenceEntry {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username
|
||||||
|
},
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The initial presence entries are sent right when the user connects.
|
||||||
|
// In the future, each user will have their own list of channels that they can join and leave.
|
||||||
|
// In that case, we will send the presence entries to a certain user only for the channels they're in.
|
||||||
|
function getInitialPresenceEntries(): GatewayPresenceEntry[] {
|
||||||
|
const entries: GatewayPresenceEntry[] = [];
|
||||||
|
|
||||||
|
sessionsByUserId.forEach((wsList: WebSocket[], userId: number) => {
|
||||||
|
if (wsList.length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const firstWs = wsList[0];
|
||||||
|
if (firstWs.state.ready && firstWs.state.user) {
|
||||||
|
entries.push(getPresenceEntryForUser(firstWs.state.user, GatewayPresenceStatus.Online));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
export default function(server: Server) {
|
export default function(server: Server) {
|
||||||
const wss = new WebSocketServer({ server });
|
const wss = new WebSocketServer({ server });
|
||||||
|
|
||||||
|
@ -163,12 +195,20 @@ export default function(server: Server) {
|
||||||
|
|
||||||
ws.on("close", () => {
|
ws.on("close", () => {
|
||||||
clientUnsubscribeAll(ws);
|
clientUnsubscribeAll(ws);
|
||||||
|
ws.state.ready = false;
|
||||||
if (ws.state.user && ws.state.user.id) {
|
if (ws.state.user && ws.state.user.id) {
|
||||||
const sessions = sessionsByUserId.get(ws.state.user.id);
|
const sessions = sessionsByUserId.get(ws.state.user.id);
|
||||||
if (sessions) {
|
if (sessions) {
|
||||||
sessions.delete(ws);
|
const index = sessions.indexOf(ws);
|
||||||
if (sessions.size < 1) {
|
sessions.splice(index, 1);
|
||||||
|
if (sessions.length < 1) {
|
||||||
sessionsByUserId.delete(ws.state.user.id);
|
sessionsByUserId.delete(ws.state.user.id);
|
||||||
|
|
||||||
|
// user no longer has any sessions, update presence
|
||||||
|
dispatch("*", {
|
||||||
|
t: GatewayPayloadType.PresenceUpdate,
|
||||||
|
d: [getPresenceEntryForUser(ws.state.user, GatewayPresenceStatus.Offline)]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,14 +251,14 @@ export default function(server: Server) {
|
||||||
|
|
||||||
let sessions = sessionsByUserId.get(user.id);
|
let sessions = sessionsByUserId.get(user.id);
|
||||||
if (sessions) {
|
if (sessions) {
|
||||||
if ((sessions.size + 1) > MAX_GATEWAY_SESSIONS_PER_USER) {
|
if ((sessions.length + 1) > MAX_GATEWAY_SESSIONS_PER_USER) {
|
||||||
return closeWithError(ws, gatewayErrors.TOO_MANY_SESSIONS);
|
return closeWithError(ws, gatewayErrors.TOO_MANY_SESSIONS);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sessions = new Set();
|
sessions = [];
|
||||||
sessionsByUserId.set(user.id, sessions);
|
sessionsByUserId.set(user.id, sessions);
|
||||||
}
|
}
|
||||||
sessions.add(ws);
|
sessions.push(ws);
|
||||||
|
|
||||||
// TODO: each user should have their own list of channels that they join
|
// TODO: each user should have their own list of channels that they join
|
||||||
const channels = await query("SELECT id, name, owner_id FROM channels ORDER BY id ASC");
|
const channels = await query("SELECT id, name, owner_id FROM channels ORDER BY id ASC");
|
||||||
|
@ -233,15 +273,25 @@ export default function(server: Server) {
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.state.user = user;
|
ws.state.user = user;
|
||||||
|
|
||||||
|
// first session, notify others that we are online
|
||||||
|
if (sessions.length === 1) {
|
||||||
|
dispatch("*", {
|
||||||
|
t: GatewayPayloadType.PresenceUpdate,
|
||||||
|
d: [getPresenceEntryForUser(ws.state.user, GatewayPresenceStatus.Online)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ws.state.ready = true;
|
ws.state.ready = true;
|
||||||
|
|
||||||
sendPayload(ws, {
|
sendPayload(ws, {
|
||||||
t: GatewayPayloadType.Ready,
|
t: GatewayPayloadType.Ready,
|
||||||
d: {
|
d: {
|
||||||
user: getPublicUserObject(ws.state.user),
|
user: getPublicUserObject(ws.state.user),
|
||||||
channels: channels.rows
|
channels: channels.rows,
|
||||||
|
presence: getInitialPresenceEntries()
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GatewayPayloadType.Ping: {
|
case GatewayPayloadType.Ping: {
|
||||||
|
|
Loading…
Reference in a new issue