diff --git a/frontend/src/components/App.svelte b/frontend/src/components/App.svelte index aa7eea5..c8be979 100644 --- a/frontend/src/components/App.svelte +++ b/frontend/src/components/App.svelte @@ -1,6 +1,6 @@ - +
diff --git a/frontend/src/components/ChannelView.svelte b/frontend/src/components/pages/main/ChannelView.svelte similarity index 92% rename from frontend/src/components/ChannelView.svelte rename to frontend/src/components/pages/main/ChannelView.svelte index 712ca70..e63cca5 100644 --- a/frontend/src/components/ChannelView.svelte +++ b/frontend/src/components/pages/main/ChannelView.svelte @@ -1,5 +1,7 @@ - - - diff --git a/frontend/src/gateway.js b/frontend/src/gateway.js index 7a678d1..1dc9e4b 100644 --- a/frontend/src/gateway.js +++ b/frontend/src/gateway.js @@ -15,6 +15,13 @@ export const GatewayPayloadType = { MessageDelete: 122, } +export const GatewayEventType = { + ...GatewayPayloadType, + + Open: -5, + Close: -4 +} + export default { ws: null, authenticated: false, @@ -23,7 +30,10 @@ export default { channels: null, reconnectDelay: 400, reconnectTimeout: null, + handlers: new Map(), init() { + window.__WAFFLE_GATEWAY = this; + const token = getAuthToken(); if (!token) { return false; @@ -33,6 +43,7 @@ export default { if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); } + this.dispatch(GatewayEventType.Open, null); console.log("[gateway] open"); }; this.ws.onmessage = (event) => { @@ -65,6 +76,8 @@ export default { break; } } + + this.dispatch(payload.t, payload.d); }; this.ws.onclose = () => { if (this.reconnectDelay < 60000) { @@ -80,6 +93,8 @@ export default { this.init(); }, this.reconnectDelay); + this.dispatch(GatewayEventType.Close, null); + console.log("[gateway] close"); }; @@ -87,5 +102,33 @@ export default { }, send(data) { return this.ws.send(JSON.stringify(data)); + }, + dispatch(event, payload) { + const eventHandlers = this.handlers.get(event); + if (!eventHandlers) + return; + + eventHandlers.forEach((e) => { + e(payload); + }); + }, + subscribe(event, handler) { + if (!this.handlers.get(event)) { + this.handlers.set(event, new Set()); + } + + this.handlers.get(event).add(handler); + return handler; // can later be used for unsubscribe() + }, + unsubscribe(event, handler) { + const eventHandlers = this.handlers.get(event); + if (!eventHandlers) + return; + + eventHandlers.delete(handler); + + if (eventHandlers.size < 1) { + this.handlers.delete(event); + } } }; diff --git a/frontend/src/stores.js b/frontend/src/stores.js new file mode 100644 index 0000000..db36e8c --- /dev/null +++ b/frontend/src/stores.js @@ -0,0 +1,69 @@ +import gateway, { GatewayEventType } from "./gateway"; + +export const channels = { + _handlers: [], + _value: [], + _didInit: false, + subscribe(handler) { + this._init(); + + const newLength = this._handlers.push(handler); + const handlerIndex = newLength - 1; + handler(this._value); + return () => { + this._handlers.splice(handlerIndex, 1); + }; + }, + _dispatchUpdate() { + this._handlers.forEach(e => { + e(this._value); + }); + }, + _init() { + window.__WAFFLE_CHANNELS_STORE = this; + + if (this._didInit) + return; + + this._didInit = true; + + const channels = gateway.channels; + if (channels) { + this._value = channels; + } + + gateway.subscribe(GatewayEventType.Ready, ({ channels }) => { + if (channels) { + this._value = channels; + } + + this._dispatchUpdate(); + }); + gateway.subscribe(GatewayEventType.ChannelCreate, (channel) => { + this._value.push(channel); + + this._dispatchUpdate(); + }); + gateway.subscribe(GatewayEventType.ChannelDelete, ({ id }) => { + const index = this._value.findIndex(e => e.id === id); + if (!index) + return; + + this._value.splice(index); + + this._dispatchUpdate(); + }); + gateway.subscribe(GatewayEventType.ChannelUpdate, (data) => { + const index = this._value.findIndex(e => e.id === data.id); + if (!index) + return; + + if (!this._value[index]) + return; + + this._value[index] = data; + + this._dispatchUpdate(); + }); + } +}; diff --git a/test.rest b/test.rest index abab5c2..28ad50b 100644 --- a/test.rest +++ b/test.rest @@ -19,50 +19,50 @@ content-type: application/json ### GET http://localhost:3000/api/v1/users/self HTTP/1.1 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk ### POST http://localhost:3000/api/v1/channels HTTP/1.1 content-type: application/json -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk { - "name": "yet another channelllll" + "name": "idk what to call these" } ### -PUT http://localhost:3000/api/v1/channels/8 HTTP/1.1 +PUT http://localhost:3000/api/v1/channels/5 HTTP/1.1 content-type: application/json -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk #Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY { - "name": "this is my channelaaaaaa" + "name": "idk what to put here" } ### -DELETE http://localhost:3000/api/v1/channels/1 HTTP/1.1 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +DELETE http://localhost:3000/api/v1/channels/10 HTTP/1.1 +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk #Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY ### GET http://localhost:3000/api/v1/channels/1 HTTP/1.1 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk ### GET http://localhost:3000/api/v1/channels HTTP/1.1 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk ### POST http://localhost:3000/api/v1/channels/5/messages HTTP/1.1 content-type: application/json -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk { "content": "i hate cheese" @@ -71,13 +71,13 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6M ### GET http://localhost:3000/api/v1/channels/5/messages HTTP/1.1 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk ### PUT http://localhost:3000/api/v1/messages/3 HTTP/1.1 content-type: application/json -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk { "content": "hello again!" @@ -86,9 +86,9 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6M ### GET http://localhost:3000/api/v1/messages/3 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk ### DELETE http://localhost:3000/api/v1/messages/2 HTTP/1.1 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjUwMTUyNjQ2LCJleHAiOjE2NTAzMjU0NDZ9.eFjOjJV3rIZWN0WrdRblflh2q8dYDGQv4qxugjbYNFk