From 575e9e20102e61ff187724f963eec492582af007835a67f902db78770fc1b6b7 Mon Sep 17 00:00:00 2001 From: aslan Date: Tue, 13 Jan 2026 17:33:23 -0500 Subject: [PATCH] Added end to end encryption --- favicon.ico | Bin 0 -> 1094 bytes package-lock.json | 4 +- package.json | 2 +- src/api/auth/auth.ts | 18 +- src/api/auth/types.ts | 27 ++- src/api/channel/channel.ts | 11 +- src/api/channel/types.ts | 13 +- src/api/community/community.ts | 17 +- src/api/community/types.ts | 18 +- src/api/invite/invite.ts | 7 +- src/api/invite/types.ts | 8 +- src/api/message/message.ts | 9 +- src/api/message/types.ts | 12 +- src/api/role/role.ts | 9 +- src/api/role/types.ts | 10 +- src/api/session/session.ts | 5 +- src/api/session/types.ts | 9 +- src/api/types.ts | 9 + src/api/user/types.ts | 14 +- src/api/user/user.ts | 11 +- src/components/Community/Community.tsx | 2 +- src/components/CommunityBar/CommunityBar.tsx | 37 +++- .../CommunityModal/CommunityModal.tsx | 119 ----------- src/components/CommunityModal/index.ts | 2 - src/components/CommunityModal/types.ts | 6 - .../CommunitySettingsModal/index.ts | 2 - .../CommunitySettingsModal/types.ts | 6 - src/components/HomeCard/HomeCard.tsx | 2 +- src/components/HomeCard/types.ts | 2 +- src/components/Input/Input.tsx | 40 ++++ src/components/Input/index.ts | 2 + src/components/Input/types.ts | 12 ++ src/components/Message/Message.tsx | 10 +- src/components/Message/types.ts | 1 + src/components/MessageBar/MessageBar.tsx | 7 +- .../RichSettingsItem/RichSettingsItem.tsx | 64 ++++++ src/components/RichSettingsItem/index.ts | 2 + src/components/RichSettingsItem/types.ts | 17 ++ src/components/SettingsItem/SettingsItem.tsx | 17 ++ src/components/SettingsItem/index.ts | 2 + src/components/SettingsItem/types.ts | 8 + .../SettingsModal/SettingsModal.tsx | 22 --- src/components/SettingsModal/index.ts | 2 - src/components/SidebarItem/SidebarItem.tsx | 13 +- src/components/SidebarItem/types.ts | 3 +- src/components/icons/index.ts | 7 - src/icons/DeviceIcon.tsx | 34 ++++ src/{components => }/icons/HomeIcon.tsx | 0 src/icons/MinusIcon.tsx | 34 ++++ src/{components => }/icons/PlusIcon.tsx | 0 src/{components => }/icons/SettingsIcon.tsx | 0 src/icons/TrashIcon.tsx | 31 +++ src/icons/UpIcon.tsx | 34 ++++ src/icons/index.ts | 20 ++ src/{components => }/icons/types.ts | 0 src/services/auth/auth.ts | 82 ++++++-- src/services/channel/channel.ts | 85 +++++--- src/services/community/community.ts | 149 ++++++++------ src/services/crypto/crypto.ts | 95 +++++++++ src/services/crypto/index.ts | 1 + src/services/crypto/types.ts | 13 ++ src/services/database/database.ts | 114 +++++++++++ src/services/database/index.ts | 2 + src/services/database/types.ts | 10 + src/services/invite/invite.ts | 36 ++-- src/services/message/message.ts | 124 ++++++++++-- src/services/role/role.ts | 39 ++-- src/services/session/session.ts | 21 +- src/services/user/user.ts | 58 +++--- src/services/websocket/types.ts | 36 +++- src/services/websocket/websocket.ts | 74 +++++-- src/store/actions.ts | 33 ---- src/store/app/actions.ts | 17 -- src/store/app/app.ts | 51 +++-- src/store/app/index.ts | 1 - src/store/auth/actions.ts | 29 --- src/store/auth/auth.ts | 67 ++++--- src/store/auth/index.ts | 1 - src/store/auth/types.ts | 10 +- src/store/channel/actions.ts | 79 -------- src/store/channel/channel.ts | 145 ++++++-------- src/store/channel/index.ts | 1 - src/store/channel/types.ts | 4 +- src/store/community/actions.ts | 104 ---------- src/store/community/community.ts | 185 ++++++++---------- src/store/community/index.ts | 1 - src/store/community/types.ts | 4 +- src/store/invite/actions.ts | 39 ---- src/store/invite/index.ts | 1 - src/store/invite/invite.ts | 41 +--- src/store/invite/types.ts | 2 +- src/store/message/actions.ts | 49 ----- src/store/message/index.ts | 1 - src/store/message/message.ts | 73 ++----- src/store/message/types.ts | 2 + src/store/reducers.ts | 25 --- src/store/role/actions.ts | 49 ----- src/store/role/index.ts | 1 - src/store/role/role.ts | 53 +---- src/store/role/types.ts | 2 +- src/store/session/actions.ts | 31 --- src/store/session/index.ts | 1 - src/store/session/session.ts | 36 +--- src/store/session/types.ts | 5 +- src/store/state.ts | 9 +- src/store/user/actions.ts | 44 ----- src/store/user/index.ts | 1 - src/store/user/user.ts | 72 +++---- .../AddCommunityModalView.tsx | 90 +++++++++ src/views/AddCommunityModalView/index.ts | 2 + src/views/AddCommunityModalView/types.ts | 6 + src/views/AppView/AppView.tsx | 17 +- src/views/ChannelView/ChannelView.tsx | 30 +-- src/views/ChatView/ChatView.tsx | 85 ++++---- .../CommunitySettingsModalView.tsx} | 15 +- src/views/CommunitySettingsModalView/index.ts | 2 + src/views/CommunitySettingsModalView/types.ts | 6 + src/views/CommunityView/CommunityView.tsx | 67 +++---- src/views/HomeView/HomeView.tsx | 38 ++-- src/views/LoginView/LoginView.tsx | 98 ++++++---- src/views/MemberView/MemberView.tsx | 16 +- src/views/ModalView/ModalView.tsx | 47 ++--- src/views/RegisterView/RegisterView.tsx | 92 ++++++++- .../SettingsModalView/SettingsModalView.tsx | 69 +++++++ src/views/SettingsModalView/index.ts | 2 + .../SettingsServersPage.tsx | 132 +++++++++++++ .../pages/SettingsServersPage/index.ts | 1 + .../SettingsSessionsPage.tsx | 125 ++++++++++++ .../pages/SettingsSessionsPage/index.ts | 1 + .../SettingsModalView}/types.ts | 4 +- src/views/StartView/StartView.tsx | 33 +++- 131 files changed, 2289 insertions(+), 1670 deletions(-) create mode 100644 favicon.ico create mode 100644 src/api/types.ts delete mode 100644 src/components/CommunityModal/CommunityModal.tsx delete mode 100644 src/components/CommunityModal/index.ts delete mode 100644 src/components/CommunityModal/types.ts delete mode 100644 src/components/CommunitySettingsModal/index.ts delete mode 100644 src/components/CommunitySettingsModal/types.ts create mode 100644 src/components/Input/Input.tsx create mode 100644 src/components/Input/index.ts create mode 100644 src/components/Input/types.ts create mode 100644 src/components/RichSettingsItem/RichSettingsItem.tsx create mode 100644 src/components/RichSettingsItem/index.ts create mode 100644 src/components/RichSettingsItem/types.ts create mode 100644 src/components/SettingsItem/SettingsItem.tsx create mode 100644 src/components/SettingsItem/index.ts create mode 100644 src/components/SettingsItem/types.ts delete mode 100644 src/components/SettingsModal/SettingsModal.tsx delete mode 100644 src/components/SettingsModal/index.ts delete mode 100644 src/components/icons/index.ts create mode 100644 src/icons/DeviceIcon.tsx rename src/{components => }/icons/HomeIcon.tsx (100%) create mode 100644 src/icons/MinusIcon.tsx rename src/{components => }/icons/PlusIcon.tsx (100%) rename src/{components => }/icons/SettingsIcon.tsx (100%) create mode 100644 src/icons/TrashIcon.tsx create mode 100644 src/icons/UpIcon.tsx create mode 100644 src/icons/index.ts rename src/{components => }/icons/types.ts (100%) create mode 100644 src/services/crypto/crypto.ts create mode 100644 src/services/crypto/index.ts create mode 100644 src/services/crypto/types.ts create mode 100644 src/services/database/database.ts create mode 100644 src/services/database/index.ts create mode 100644 src/services/database/types.ts delete mode 100644 src/store/actions.ts delete mode 100644 src/store/app/actions.ts delete mode 100644 src/store/auth/actions.ts delete mode 100644 src/store/channel/actions.ts delete mode 100644 src/store/community/actions.ts delete mode 100644 src/store/invite/actions.ts delete mode 100644 src/store/message/actions.ts delete mode 100644 src/store/reducers.ts delete mode 100644 src/store/role/actions.ts delete mode 100644 src/store/session/actions.ts delete mode 100644 src/store/user/actions.ts create mode 100644 src/views/AddCommunityModalView/AddCommunityModalView.tsx create mode 100644 src/views/AddCommunityModalView/index.ts create mode 100644 src/views/AddCommunityModalView/types.ts rename src/{components/CommunitySettingsModal/CommunitySettingsModal.tsx => views/CommunitySettingsModalView/CommunitySettingsModalView.tsx} (61%) create mode 100644 src/views/CommunitySettingsModalView/index.ts create mode 100644 src/views/CommunitySettingsModalView/types.ts create mode 100644 src/views/SettingsModalView/SettingsModalView.tsx create mode 100644 src/views/SettingsModalView/index.ts create mode 100644 src/views/SettingsModalView/pages/SettingsServersPage/SettingsServersPage.tsx create mode 100644 src/views/SettingsModalView/pages/SettingsServersPage/index.ts create mode 100644 src/views/SettingsModalView/pages/SettingsSessionsPage/SettingsSessionsPage.tsx create mode 100644 src/views/SettingsModalView/pages/SettingsSessionsPage/index.ts rename src/{components/SettingsModal => views/SettingsModalView}/types.ts (51%) diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..0e4f6bd81ab9593bb6f1dc1610d916325c9cb9902045618fa82d3ceec9f4a505 GIT binary patch literal 1094 ucmZQzU<5)32PQCWz{0>F#=yX!0mKeK+yKNN`B5+$0{BAU|0o#IApihja{tNz literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index ad5d8db..16fcc7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pulsar-web", - "version": "0.3.0", + "version": "0.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pulsar-web", - "version": "0.3.0", + "version": "0.5.2", "license": "MIT", "dependencies": { "@solidjs/router": "^0.15.4", diff --git a/package.json b/package.json index 3324c9f..be404bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulsar-web", - "version": "0.4.0", + "version": "0.5.2", "description": "", "type": "module", "scripts": { diff --git a/src/api/auth/auth.ts b/src/api/auth/auth.ts index f402811..2f4d197 100644 --- a/src/api/auth/auth.ts +++ b/src/api/auth/auth.ts @@ -1,21 +1,29 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { + IFetchRegisterRequest, + IFetchRegisterResponse, IFetchLoginRequest, IFetchLoginResponse, - IFetchRefreshResponseError, - IFetchRefreshResponseSuccess, + IFetchRefreshResponse, } from "./types"; +const fetchRegisterApi = async ( + request: IFetchRegisterRequest, +): Promise => { + return await callApi(HTTP.POST, `auth/register`, request, true); +}; + const fetchLoginApi = async ( request: IFetchLoginRequest, -): Promise => { +): Promise => { return await callApi(HTTP.POST, `auth/login`, request, true); }; const fetchRefreshApi = async (): Promise< - IFetchRefreshResponseError | IFetchRefreshResponseSuccess + IFetchRefreshResponse | IResponseError > => { return await callApi(HTTP.GET, `auth/refresh`, undefined, true); }; -export { fetchLoginApi, fetchRefreshApi }; +export { fetchRegisterApi, fetchLoginApi, fetchRefreshApi }; diff --git a/src/api/auth/types.ts b/src/api/auth/types.ts index 1a4aa67..c7e059f 100644 --- a/src/api/auth/types.ts +++ b/src/api/auth/types.ts @@ -1,26 +1,37 @@ +import { IResponseSuccess } from "../types"; + +interface IFetchRegisterRequest { + username: string; + password: string; + email: string; +} + +interface IFetchRegisterResponse extends IResponseSuccess { + id: string; + ownerId: string; +} + interface IFetchLoginRequest { username: string; password: string; } -interface IFetchLoginResponse { +interface IFetchLoginResponse extends IResponseSuccess { id: string; ownerId: string; } -interface IFetchRefreshResponseError { - error: string; -} - -interface IFetchRefreshResponseSuccess { +interface IFetchRefreshResponse extends IResponseSuccess { id: string; ownerId: string; token: string; + storageSecret: string; } export { + type IFetchRegisterRequest, + type IFetchRegisterResponse, type IFetchLoginRequest, type IFetchLoginResponse, - type IFetchRefreshResponseError, - type IFetchRefreshResponseSuccess, + type IFetchRefreshResponse, }; diff --git a/src/api/channel/channel.ts b/src/api/channel/channel.ts index 53654f8..39f7100 100644 --- a/src/api/channel/channel.ts +++ b/src/api/channel/channel.ts @@ -1,4 +1,5 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { IFetchChannelRequest, IFetchChannelResponse, @@ -14,31 +15,31 @@ import { const fetchChannelApi = async ( request: IFetchChannelRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `channel/${request.id}`); }; const createChannelApi = async ( request: ICreateChannelRequest, -): Promise => { +): Promise => { return await callApi(HTTP.POST, `channel`, request); }; const updateChannelApi = async ( request: IUpdateChannelRequest, -): Promise => { +): Promise => { return await callApi(HTTP.PATCH, `channel/${request.id}`, request); }; const removeChannelApi = async ( request: IRemoveChannelRequest, -): Promise => { +): Promise => { return await callApi(HTTP.DELETE, `channel/${request.id}`); }; const fetchChannelMessagesApi = async ( request: IFetchChannelMessagesRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `channel/${request.id}/messages`); }; diff --git a/src/api/channel/types.ts b/src/api/channel/types.ts index 2f451cb..d46668a 100644 --- a/src/api/channel/types.ts +++ b/src/api/channel/types.ts @@ -1,3 +1,5 @@ +import { IResponseSuccess } from "../types"; + interface IFetchChannel { id: string; name: string; @@ -10,14 +12,14 @@ interface IFetchChannelRequest { id: string; } -interface IFetchChannelResponse extends IFetchChannel {} +interface IFetchChannelResponse extends IResponseSuccess, IFetchChannel {} interface ICreateChannelRequest { name: string; communityId: string; } -interface ICreateChannelResponse extends IFetchChannel {} +interface ICreateChannelResponse extends IResponseSuccess, IFetchChannel {} interface IUpdateChannelRequest { id: string; @@ -25,13 +27,13 @@ interface IUpdateChannelRequest { description?: string; } -interface IUpdateChannelResponse extends IFetchChannel {} +interface IUpdateChannelResponse extends IResponseSuccess, IFetchChannel {} interface IRemoveChannelRequest { id: string; } -interface IRemoveChannelResponse { +interface IRemoveChannelResponse extends IResponseSuccess { id: string; communityId: string; } @@ -40,7 +42,7 @@ interface IFetchChannelMessagesRequest { id: string; } -interface IFetchChannelMessagesResponse { +interface IFetchChannelMessagesResponse extends IResponseSuccess { id: string; messages: IFetchChannelMessage[]; } @@ -48,6 +50,7 @@ interface IFetchChannelMessagesResponse { interface IFetchChannelMessage { id: string; text: string; + iv: string; edited: boolean; ownerId: string; creationDate: number; diff --git a/src/api/community/community.ts b/src/api/community/community.ts index 8bd67d0..135a623 100644 --- a/src/api/community/community.ts +++ b/src/api/community/community.ts @@ -1,4 +1,5 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { IFetchCommunityRequest, IFetchCommunityResponse, @@ -20,49 +21,49 @@ import { const fetchCommunityApi = async ( request: IFetchCommunityRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `community/${request.id}`); }; const createCommunityApi = async ( request: ICreateCommunityRequest, -): Promise => { +): Promise => { return await callApi(HTTP.POST, `community`, request); }; const updateCommunityApi = async ( request: IUpdateCommunityRequest, -): Promise => { +): Promise => { return await callApi(HTTP.PATCH, `community/${request.id}`, request); }; const removeCommunityApi = async ( request: IRemoveCommunityRequest, -): Promise => { +): Promise => { return await callApi(HTTP.DELETE, `community/${request.id}`); }; const fetchCommunityChannelsApi = async ( request: IFetchCommunityChannelsRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `community/${request.id}/channels`); }; const fetchCommunityRolesApi = async ( request: IFetchCommunityRolesRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `community/${request.id}/roles`); }; const fetchCommunityMembersApi = async ( request: IFetchCommunityMembersRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `community/${request.id}/members`); }; const fetchCommunityInvitesApi = async ( request: IFetchCommunityInvitesRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `community/${request.id}/invites`); }; diff --git a/src/api/community/types.ts b/src/api/community/types.ts index 7666e3f..f7fb5e7 100644 --- a/src/api/community/types.ts +++ b/src/api/community/types.ts @@ -1,3 +1,5 @@ +import { IResponseSuccess } from "../types"; + interface IFetchCommunity { id: string; name: string; @@ -10,13 +12,13 @@ interface IFetchCommunityRequest { id: string; } -interface IFetchCommunityResponse extends IFetchCommunity {} +interface IFetchCommunityResponse extends IResponseSuccess, IFetchCommunity {} interface ICreateCommunityRequest { name: string; } -interface ICreateCommunityResponse extends IFetchCommunity {} +interface ICreateCommunityResponse extends IResponseSuccess, IFetchCommunity {} interface IUpdateCommunityRequest { id: string; @@ -24,13 +26,13 @@ interface IUpdateCommunityRequest { description?: string; } -interface IUpdateCommunityResponse extends IFetchCommunity {} +interface IUpdateCommunityResponse extends IResponseSuccess, IFetchCommunity {} interface IRemoveCommunityRequest { id: string; } -interface IRemoveCommunityResponse { +interface IRemoveCommunityResponse extends IResponseSuccess { id: string; } @@ -38,7 +40,7 @@ interface IFetchCommunityChannelsRequest { id: string; } -interface IFetchCommunityChannelsResponse { +interface IFetchCommunityChannelsResponse extends IResponseSuccess { id: string; channels: IFetchCommunityChannel[]; } @@ -52,7 +54,7 @@ interface IFetchCommunityRolesRequest { id: string; } -interface IFetchCommunityRolesResponse { +interface IFetchCommunityRolesResponse extends IResponseSuccess { id: string; roles: IFetchCommunityRole[]; } @@ -66,7 +68,7 @@ interface IFetchCommunityMembersRequest { id: string; } -interface IFetchCommunityMembersResponse { +interface IFetchCommunityMembersResponse extends IResponseSuccess { id: string; members: IFetchCommunityMember[]; } @@ -80,7 +82,7 @@ interface IFetchCommunityInvitesRequest { id: string; } -interface IFetchCommunityInvitesResponse { +interface IFetchCommunityInvitesResponse extends IResponseSuccess { id: string; invites: IFetchCommunityInvite[]; } diff --git a/src/api/invite/invite.ts b/src/api/invite/invite.ts index 0774817..1e8dc5c 100644 --- a/src/api/invite/invite.ts +++ b/src/api/invite/invite.ts @@ -1,4 +1,5 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { IFetchInviteRequest, IFetchInviteResponse, @@ -10,19 +11,19 @@ import { const fetchInviteApi = async ( request: IFetchInviteRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `invite/${request.id}`); }; const removeInviteApi = async ( request: IRemoveInviteRequest, -): Promise => { +): Promise => { return await callApi(HTTP.DELETE, `invite/${request.id}`); }; const acceptInviteApi = async ( request: IAcceptInviteRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `invite/${request.id}/accept`); }; diff --git a/src/api/invite/types.ts b/src/api/invite/types.ts index a79ad1a..8a86e62 100644 --- a/src/api/invite/types.ts +++ b/src/api/invite/types.ts @@ -1,3 +1,5 @@ +import { IResponseSuccess } from "../types"; + interface IFetchInvite { id: string; communityId: string; @@ -14,13 +16,13 @@ interface IFetchInviteRequest { id: string; } -interface IFetchInviteResponse extends IFetchInvite {} +interface IFetchInviteResponse extends IResponseSuccess, IFetchInvite {} interface IRemoveInviteRequest { id: string; } -interface IRemoveInviteResponse { +interface IRemoveInviteResponse extends IResponseSuccess { id: string; userId: string; } @@ -29,7 +31,7 @@ interface IAcceptInviteRequest { id: string; } -interface IAcceptInviteResponse { +interface IAcceptInviteResponse extends IResponseSuccess { id: string; userId: string; userName: string; diff --git a/src/api/message/message.ts b/src/api/message/message.ts index 442c510..83b53f7 100644 --- a/src/api/message/message.ts +++ b/src/api/message/message.ts @@ -1,4 +1,5 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { IFetchMessageRequest, IFetchMessageResponse, @@ -12,25 +13,25 @@ import { const fetchMessageApi = async ( request: IFetchMessageRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `message/${request.id}`); }; const createMessageApi = async ( request: ICreateMessageRequest, -): Promise => { +): Promise => { return await callApi(HTTP.POST, `message`, request); }; const updateMessageApi = async ( request: IUpdateMessageRequest, -): Promise => { +): Promise => { return await callApi(HTTP.PATCH, `message/${request.id}`, request); }; const removeMessageApi = async ( request: IRemoveMessageRequest, -): Promise => { +): Promise => { return await callApi(HTTP.DELETE, `message/${request.id}`); }; diff --git a/src/api/message/types.ts b/src/api/message/types.ts index 32d1e0a..797dca6 100644 --- a/src/api/message/types.ts +++ b/src/api/message/types.ts @@ -1,6 +1,9 @@ +import { IResponseSuccess } from "../types"; + interface IFetchMessage { id: string; text: string; + iv: string; editHistory: string[]; edited: boolean; ownerId: string; @@ -12,27 +15,28 @@ interface IFetchMessageRequest { id: string; } -interface IFetchMessageResponse extends IFetchMessage {} +interface IFetchMessageResponse extends IResponseSuccess, IFetchMessage {} interface ICreateMessageRequest { text: string; + iv: string; channelId: string; } -interface ICreateMessageResponse extends IFetchMessage {} +interface ICreateMessageResponse extends IResponseSuccess, IFetchMessage {} interface IUpdateMessageRequest { id: string; text: string; } -interface IUpdateMessageResponse extends IFetchMessage {} +interface IUpdateMessageResponse extends IResponseSuccess, IFetchMessage {} interface IRemoveMessageRequest { id: string; } -interface IRemoveMessageResponse { +interface IRemoveMessageResponse extends IResponseSuccess { id: string; ownerId: string; channelId: string; diff --git a/src/api/role/role.ts b/src/api/role/role.ts index 3b50acf..0c2a9e4 100644 --- a/src/api/role/role.ts +++ b/src/api/role/role.ts @@ -1,4 +1,5 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { IFetchRoleRequest, IFetchRoleResponse, @@ -12,25 +13,25 @@ import { const fetchRoleApi = async ( request: IFetchRoleRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `role/${request.id}`); }; const createRoleApi = async ( request: ICreateRoleRequest, -): Promise => { +): Promise => { return await callApi(HTTP.POST, `role`, request); }; const updateRoleApi = async ( request: IUpdateRoleRequest, -): Promise => { +): Promise => { return await callApi(HTTP.PATCH, `role/${request.id}`, request); }; const removeRoleApi = async ( request: IRemoveRoleRequest, -): Promise => { +): Promise => { return await callApi(HTTP.DELETE, `role/${request.id}`); }; diff --git a/src/api/role/types.ts b/src/api/role/types.ts index 9eca948..60329ae 100644 --- a/src/api/role/types.ts +++ b/src/api/role/types.ts @@ -1,3 +1,5 @@ +import { IResponseSuccess } from "../types"; + interface IFetchRole { id: string; name: string; @@ -11,27 +13,27 @@ interface IFetchRoleRequest { id: string; } -interface IFetchRoleResponse extends IFetchRole {} +interface IFetchRoleResponse extends IResponseSuccess, IFetchRole {} interface ICreateRoleRequest { name: string; communityId: string; } -interface ICreateRoleResponse extends IFetchRole {} +interface ICreateRoleResponse extends IResponseSuccess, IFetchRole {} interface IUpdateRoleRequest { id: string; name?: string; } -interface IUpdateRoleResponse extends IFetchRole {} +interface IUpdateRoleResponse extends IResponseSuccess, IFetchRole {} interface IRemoveRoleRequest { id: string; } -interface IRemoveRoleResponse { +interface IRemoveRoleResponse extends IResponseSuccess { id: string; communityId: string; } diff --git a/src/api/session/session.ts b/src/api/session/session.ts index e0f33e9..35bddaa 100644 --- a/src/api/session/session.ts +++ b/src/api/session/session.ts @@ -1,4 +1,5 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { IFetchSessionRequest, IFetchSessionResponse, @@ -8,13 +9,13 @@ import { const fetchSessionApi = async ( request: IFetchSessionRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `session/${request.id}`); }; const removeSessionApi = async ( request: IRemoveSessionRequest, -): Promise => { +): Promise => { return await callApi(HTTP.DELETE, `session/${request.id}`); }; diff --git a/src/api/session/types.ts b/src/api/session/types.ts index bb6ee5d..155ed54 100644 --- a/src/api/session/types.ts +++ b/src/api/session/types.ts @@ -1,20 +1,25 @@ +import { IResponseSuccess } from "../types"; + interface IFetchSession { id: string; userId: string; + name: string; + userAgent: string; creationDate: number; + refreshDate: number; } interface IFetchSessionRequest { id: string; } -interface IFetchSessionResponse extends IFetchSession {} +interface IFetchSessionResponse extends IResponseSuccess, IFetchSession {} interface IRemoveSessionRequest { id: string; } -interface IRemoveSessionResponse { +interface IRemoveSessionResponse extends IResponseSuccess { id: string; userId: string; } diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..d1a45dc --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,9 @@ +interface IResponseError { + error: string; +} + +interface IResponseSuccess { + error: null; +} + +export { type IResponseError, type IResponseSuccess }; diff --git a/src/api/user/types.ts b/src/api/user/types.ts index bc2ffb2..ac0e08e 100644 --- a/src/api/user/types.ts +++ b/src/api/user/types.ts @@ -1,3 +1,5 @@ +import { IResponseSuccess } from "../types"; + interface IFetchUser { id: string; username: string; @@ -8,7 +10,7 @@ interface IFetchUser { lastLogin: number; } -interface IFetchLoggedUserResponse { +interface IFetchLoggedUserResponse extends IResponseSuccess { id: string; } @@ -16,13 +18,13 @@ interface IFetchUserRequest { id: string; } -interface IFetchUserResponse extends IFetchUser {} +interface IFetchUserResponse extends IResponseSuccess, IFetchUser {} interface IFetchUserSessionsRequest { id: string; } -interface IFetchUserSessionsResponse { +interface IFetchUserSessionsResponse extends IResponseSuccess { id: string; sessions: IFetchUserSession[]; } @@ -30,13 +32,17 @@ interface IFetchUserSessionsResponse { interface IFetchUserSession { id: string; userId: string; + name: string; + userAgent: string; + creationDate: number; + refreshDate: number; } interface IFetchUserCommunitiesRequest { id: string; } -interface IFetchUserCommunitiesResponse { +interface IFetchUserCommunitiesResponse extends IResponseSuccess { id: string; communities: IFetchUserCommunity[]; } diff --git a/src/api/user/user.ts b/src/api/user/user.ts index 3020875..63f8d94 100644 --- a/src/api/user/user.ts +++ b/src/api/user/user.ts @@ -1,4 +1,5 @@ import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; import { IFetchLoggedUserResponse, IFetchUserRequest, @@ -9,25 +10,27 @@ import { IFetchUserCommunitiesResponse, } from "./types"; -const fetchLoggedUserApi = async (): Promise => { +const fetchLoggedUserApi = async (): Promise< + IFetchLoggedUserResponse | IResponseError +> => { return await callApi(HTTP.GET, `user/logged`); }; const fetchUserApi = async ( request: IFetchUserRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `user/${request.id}`); }; const fetchUserSessionsApi = async ( request: IFetchUserSessionsRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `user/${request.id}/sessions`); }; const fetchUserCommunitiesApi = async ( request: IFetchUserCommunitiesRequest, -): Promise => { +): Promise => { return await callApi(HTTP.GET, `user/${request.id}/communities`); }; diff --git a/src/components/Community/Community.tsx b/src/components/Community/Community.tsx index 3fbd739..bcf9bbc 100644 --- a/src/components/Community/Community.tsx +++ b/src/components/Community/Community.tsx @@ -8,7 +8,7 @@ const Community: Component = (props: ICommunityProps) => { onClick={() => props.onCommunityClick?.(props.id)} >
diff --git a/src/components/CommunityBar/CommunityBar.tsx b/src/components/CommunityBar/CommunityBar.tsx index 95e30cc..b98bc7b 100644 --- a/src/components/CommunityBar/CommunityBar.tsx +++ b/src/components/CommunityBar/CommunityBar.tsx @@ -1,10 +1,36 @@ -import type { Component } from "solid-js"; +import { type Component, type JSXElement } from "solid-js"; import { ICommunityBarProps } from "./types"; -import { SettingsIcon } from "../icons"; +import { SettingsIcon } from "../../icons"; +import { state } from "../../store/state"; const CommunityBar: Component = ( props: ICommunityBarProps, ) => { + const settingsIconHtml = (): JSXElement | undefined => { + const activeCommunityId = state.community.active; + if (!activeCommunityId) { + return undefined; + } + + const community = state.community.communities[activeCommunityId]; + if (!community) { + return undefined; + } + + if (false) { + return undefined; + } + + return ( +
+ +
+ ); + }; + return (
= (

{props.name}

{props.description}

-
- -
+ {settingsIconHtml()} ); diff --git a/src/components/CommunityModal/CommunityModal.tsx b/src/components/CommunityModal/CommunityModal.tsx deleted file mode 100644 index 3a33f7a..0000000 --- a/src/components/CommunityModal/CommunityModal.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { createSignal, type Component } from "solid-js"; -import { ICommunityModalProps } from "./types"; -import { dispatch } from "../../store/state"; -import { CommunityActionTypes } from "../../store/community"; -import { InviteActionTypes } from "../../store/invite"; - -const CommunityModal: Component = (props) => { - const [getCommunityName, setCommunityName] = createSignal(""); - const [getInviteId, setInviteId] = createSignal(""); - - const onCreateCommunity = () => { - const communityName = getCommunityName(); - if (!communityName || communityName.trim().length < 1) { - return; - } - - dispatch({ - type: CommunityActionTypes.CREATE_COMMUNITY_START, - payload: { - name: communityName, - }, - }); - - setCommunityName(""); - - props.onClose?.(); - }; - - const onJoinCommunity = () => { - const inviteId = getInviteId(); - if (!inviteId || inviteId.trim().length < 1) { - return; - } - - dispatch({ - type: InviteActionTypes.ACCEPT_INVITE_START, - payload: inviteId, - }); - - setInviteId(""); - - props.onClose?.(); - }; - - const handleEnter = (e: KeyboardEvent, callback: () => void) => { - if (e.key === "Enter") { - callback(); - } - }; - - const createCommunityHtml = () => ( - <> -

- Create a new Community -

-
- - -
- - ); - - const joinCommunityHtml = () => ( - <> -

- Join an existing Community -

-
- - -
- - ); - - return ( -
- - - - -
- ); -}; - -export default CommunityModal; diff --git a/src/components/CommunityModal/index.ts b/src/components/CommunityModal/index.ts deleted file mode 100644 index 59e2444..0000000 --- a/src/components/CommunityModal/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./CommunityModal"; -export * from "./types"; diff --git a/src/components/CommunityModal/types.ts b/src/components/CommunityModal/types.ts deleted file mode 100644 index 58b8f77..0000000 --- a/src/components/CommunityModal/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -interface ICommunityModalProps { - dialogRef?: (element: HTMLDialogElement) => void; - onClose?: () => void; -} - -export { type ICommunityModalProps }; diff --git a/src/components/CommunitySettingsModal/index.ts b/src/components/CommunitySettingsModal/index.ts deleted file mode 100644 index 79616d6..0000000 --- a/src/components/CommunitySettingsModal/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./CommunitySettingsModal"; -export * from "./types"; diff --git a/src/components/CommunitySettingsModal/types.ts b/src/components/CommunitySettingsModal/types.ts deleted file mode 100644 index 9ab3552..0000000 --- a/src/components/CommunitySettingsModal/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -interface ICommunitySettingsModalProps { - dialogRef?: (element: HTMLDialogElement) => void; - onClose?: () => void; -} - -export { type ICommunitySettingsModalProps }; diff --git a/src/components/HomeCard/HomeCard.tsx b/src/components/HomeCard/HomeCard.tsx index e929d1b..9997b1f 100644 --- a/src/components/HomeCard/HomeCard.tsx +++ b/src/components/HomeCard/HomeCard.tsx @@ -5,7 +5,7 @@ import { Dynamic } from "solid-js/web"; const HomeCard: Component = (props: IHomeCardProps) => { return ( -
+
diff --git a/src/components/HomeCard/types.ts b/src/components/HomeCard/types.ts index 2e2890b..fee99d5 100644 --- a/src/components/HomeCard/types.ts +++ b/src/components/HomeCard/types.ts @@ -1,5 +1,5 @@ import { JSXElement } from "solid-js"; -import { IconParameters } from "../icons"; +import { IconParameters } from "../../icons"; interface IHomeCardProps { title: string; diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx new file mode 100644 index 0000000..8157ffc --- /dev/null +++ b/src/components/Input/Input.tsx @@ -0,0 +1,40 @@ +import { type Component, type JSXElement } from "solid-js"; +import { IInputProps } from "./types"; + +const Input: Component = (props: IInputProps) => { + const handleEnter = (e: KeyboardEvent) => { + if (e.key === "Enter") { + props.onSubmit?.(); + } + }; + + const submitHtml = (): JSXElement => ( + + ); + + return ( +
+ + {props.submitText ? submitHtml() : undefined} +
+ ); +}; + +export { Input }; diff --git a/src/components/Input/index.ts b/src/components/Input/index.ts new file mode 100644 index 0000000..f9fa396 --- /dev/null +++ b/src/components/Input/index.ts @@ -0,0 +1,2 @@ +export * from "./Input"; +export * from "./types"; diff --git a/src/components/Input/types.ts b/src/components/Input/types.ts new file mode 100644 index 0000000..27984ac --- /dev/null +++ b/src/components/Input/types.ts @@ -0,0 +1,12 @@ +interface IInputProps { + value: string; + type?: "text" | "password" | "email"; + outline?: boolean; + rounded?: boolean; + placeholder?: string; + submitText?: string; + onChange?: (value: string) => void; + onSubmit?: () => void; +} + +export { type IInputProps }; diff --git a/src/components/Message/Message.tsx b/src/components/Message/Message.tsx index 2c33ea6..ae45831 100644 --- a/src/components/Message/Message.tsx +++ b/src/components/Message/Message.tsx @@ -13,8 +13,14 @@ const Message: Component = (props: IMessageProps) => {
-
{props.username}
-

{props.message}

+
{props.username}
+ {props.decryptionStatus ? ( +

{props.message}

+ ) : ( +

+ Decryption failed +

+ )}
); diff --git a/src/components/Message/types.ts b/src/components/Message/types.ts index 3f041f6..9c988ad 100644 --- a/src/components/Message/types.ts +++ b/src/components/Message/types.ts @@ -4,6 +4,7 @@ interface IMessageProps { userId: string; username: string; avatar: string; + decryptionStatus: boolean; onProfileClick?: (userId: string) => void; } diff --git a/src/components/MessageBar/MessageBar.tsx b/src/components/MessageBar/MessageBar.tsx index afdd99f..ee5c81b 100644 --- a/src/components/MessageBar/MessageBar.tsx +++ b/src/components/MessageBar/MessageBar.tsx @@ -1,5 +1,6 @@ import type { Component } from "solid-js"; import { IMessageBarProps } from "./types"; +import { UpIcon } from "../../icons"; const MessageBar: Component = (props: IMessageBarProps) => { const handleEnter = (e: KeyboardEvent) => { @@ -23,10 +24,12 @@ const MessageBar: Component = (props: IMessageBarProps) => { />
diff --git a/src/components/RichSettingsItem/RichSettingsItem.tsx b/src/components/RichSettingsItem/RichSettingsItem.tsx new file mode 100644 index 0000000..a85a426 --- /dev/null +++ b/src/components/RichSettingsItem/RichSettingsItem.tsx @@ -0,0 +1,64 @@ +import type { Component, JSXElement } from "solid-js"; +import { IRichSettingsItemProps } from "./types"; +import { Dynamic } from "solid-js/web"; + +const RichSettingsItem: Component = ( + props: IRichSettingsItemProps, +) => { + const pictureHtml = (): JSXElement => { + if (props.avatar) { + return ( +
+
+ +
+
+ ); + } + + if (props.icon) { + return ( +
+ +
+ ); + } + + return undefined; + }; + + const infoHtml = (): JSXElement | undefined => { + if (!props.info) { + return undefined; + } + + return ( + <> +
+
+ {props.info} +
+ + ); + }; + + return ( +
+ +
+ {pictureHtml()} + {props.title} + {infoHtml()} +
+
+
{props.children}
+
+
+ ); +}; + +export { RichSettingsItem }; diff --git a/src/components/RichSettingsItem/index.ts b/src/components/RichSettingsItem/index.ts new file mode 100644 index 0000000..dc12590 --- /dev/null +++ b/src/components/RichSettingsItem/index.ts @@ -0,0 +1,2 @@ +export * from "./RichSettingsItem"; +export * from "./types"; diff --git a/src/components/RichSettingsItem/types.ts b/src/components/RichSettingsItem/types.ts new file mode 100644 index 0000000..678d710 --- /dev/null +++ b/src/components/RichSettingsItem/types.ts @@ -0,0 +1,17 @@ +import { JSXElement } from "solid-js"; +import { IconParameters } from "../../icons"; + +interface IRichSettingsItemProps { + id: string; + title: string; + text?: string; + info?: string; + avatar?: string; + icon?: (props: IconParameters) => JSXElement; + iconPadding?: number; + active: boolean; + children?: JSXElement; + onClick?: (id: string) => void; +} + +export { type IRichSettingsItemProps }; diff --git a/src/components/SettingsItem/SettingsItem.tsx b/src/components/SettingsItem/SettingsItem.tsx new file mode 100644 index 0000000..b7348a6 --- /dev/null +++ b/src/components/SettingsItem/SettingsItem.tsx @@ -0,0 +1,17 @@ +import type { Component } from "solid-js"; +import { ISettingsItemProps } from "./types"; + +const SettingsItem: Component = ( + props: ISettingsItemProps, +) => { + return ( +
props.onClick?.(props.id)} + > + {props.text} +
+ ); +}; + +export { SettingsItem }; diff --git a/src/components/SettingsItem/index.ts b/src/components/SettingsItem/index.ts new file mode 100644 index 0000000..2c29889 --- /dev/null +++ b/src/components/SettingsItem/index.ts @@ -0,0 +1,2 @@ +export * from "./SettingsItem"; +export * from "./types"; diff --git a/src/components/SettingsItem/types.ts b/src/components/SettingsItem/types.ts new file mode 100644 index 0000000..a074789 --- /dev/null +++ b/src/components/SettingsItem/types.ts @@ -0,0 +1,8 @@ +interface ISettingsItemProps { + id: string; + text: string; + active: boolean; + onClick?: (id: string) => void; +} + +export { type ISettingsItemProps }; diff --git a/src/components/SettingsModal/SettingsModal.tsx b/src/components/SettingsModal/SettingsModal.tsx deleted file mode 100644 index 911ac06..0000000 --- a/src/components/SettingsModal/SettingsModal.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Component } from "solid-js"; -import { ISettingsModalProps } from "./types"; - -const SettingsModal: Component = (props) => { - return ( -
- - - - -
- ); -}; - -export default SettingsModal; diff --git a/src/components/SettingsModal/index.ts b/src/components/SettingsModal/index.ts deleted file mode 100644 index e0e8c4a..0000000 --- a/src/components/SettingsModal/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./SettingsModal"; -export * from "./types"; diff --git a/src/components/SidebarItem/SidebarItem.tsx b/src/components/SidebarItem/SidebarItem.tsx index 3fe3472..518acdc 100644 --- a/src/components/SidebarItem/SidebarItem.tsx +++ b/src/components/SidebarItem/SidebarItem.tsx @@ -5,9 +5,20 @@ import { Dynamic } from "solid-js/web"; const SidebarItem: Component = ( props: ISidebarItemProps, ) => { + const rotateCss = (): string => { + switch (props.hoverRotate) { + case 30: + return "hover:rotate-30"; + case 45: + return "hover:rotate-45"; + } + + return ""; + }; + return (
props.onClick?.()} > diff --git a/src/components/SidebarItem/types.ts b/src/components/SidebarItem/types.ts index 3ff2224..82abd4d 100644 --- a/src/components/SidebarItem/types.ts +++ b/src/components/SidebarItem/types.ts @@ -1,9 +1,10 @@ import { JSXElement } from "solid-js"; -import { IconParameters } from "../icons"; +import { IconParameters } from "../../icons"; interface ISidebarItemProps { icon: (props: IconParameters) => JSXElement; active: boolean; + hoverRotate?: 30 | 45; onClick?: () => void; } diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts deleted file mode 100644 index d9d5e2a..0000000 --- a/src/components/icons/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import HomeIcon from "./HomeIcon"; -import SettingsIcon from "./SettingsIcon"; -import PlusIcon from "./PlusIcon"; - -import type { IconParameters } from "./types"; - -export { IconParameters, HomeIcon, SettingsIcon, PlusIcon }; diff --git a/src/icons/DeviceIcon.tsx b/src/icons/DeviceIcon.tsx new file mode 100644 index 0000000..d02c59c --- /dev/null +++ b/src/icons/DeviceIcon.tsx @@ -0,0 +1,34 @@ +import type { Component } from "solid-js"; + +import { + IconParameters, + defaultStrokeIconParameters as defaults, +} from "./types"; + +const DeviceIcon: Component = ({ + width, + height, + fill = defaults.fill, + stroke = defaults.stroke, + strokeWidth = defaults.strokeWidth, +}: IconParameters) => { + return ( + + + + ); +}; + +export default DeviceIcon; diff --git a/src/components/icons/HomeIcon.tsx b/src/icons/HomeIcon.tsx similarity index 100% rename from src/components/icons/HomeIcon.tsx rename to src/icons/HomeIcon.tsx diff --git a/src/icons/MinusIcon.tsx b/src/icons/MinusIcon.tsx new file mode 100644 index 0000000..e74f895 --- /dev/null +++ b/src/icons/MinusIcon.tsx @@ -0,0 +1,34 @@ +import type { Component } from "solid-js"; + +import { + IconParameters, + defaultStrokeIconParameters as defaults, +} from "./types"; + +const MinusIcon: Component = ({ + width, + height, + fill = defaults.fill, + stroke = defaults.stroke, + strokeWidth = defaults.strokeWidth, +}: IconParameters) => { + return ( + + + + ); +}; + +export default MinusIcon; diff --git a/src/components/icons/PlusIcon.tsx b/src/icons/PlusIcon.tsx similarity index 100% rename from src/components/icons/PlusIcon.tsx rename to src/icons/PlusIcon.tsx diff --git a/src/components/icons/SettingsIcon.tsx b/src/icons/SettingsIcon.tsx similarity index 100% rename from src/components/icons/SettingsIcon.tsx rename to src/icons/SettingsIcon.tsx diff --git a/src/icons/TrashIcon.tsx b/src/icons/TrashIcon.tsx new file mode 100644 index 0000000..fbb88d9 --- /dev/null +++ b/src/icons/TrashIcon.tsx @@ -0,0 +1,31 @@ +import type { Component } from "solid-js"; + +import { IconParameters, defaultFillIconParameters as defaults } from "./types"; + +const TrashIcon: Component = ({ + width, + height, + fill = defaults.fill, + stroke = defaults.stroke, + strokeWidth = defaults.strokeWidth, +}: IconParameters) => { + return ( + + + + ); +}; + +export default TrashIcon; diff --git a/src/icons/UpIcon.tsx b/src/icons/UpIcon.tsx new file mode 100644 index 0000000..b537e0e --- /dev/null +++ b/src/icons/UpIcon.tsx @@ -0,0 +1,34 @@ +import type { Component } from "solid-js"; + +import { + IconParameters, + defaultStrokeIconParameters as defaults, +} from "./types"; + +const UpIcon: Component = ({ + width, + height, + fill = defaults.fill, + stroke = defaults.stroke, + strokeWidth = defaults.strokeWidth, +}: IconParameters) => { + return ( + + + + ); +}; + +export default UpIcon; diff --git a/src/icons/index.ts b/src/icons/index.ts new file mode 100644 index 0000000..3abb71e --- /dev/null +++ b/src/icons/index.ts @@ -0,0 +1,20 @@ +import HomeIcon from "./HomeIcon"; +import SettingsIcon from "./SettingsIcon"; +import PlusIcon from "./PlusIcon"; +import MinusIcon from "./MinusIcon"; +import DeviceIcon from "./DeviceIcon"; +import TrashIcon from "./TrashIcon"; +import UpIcon from "./UpIcon"; + +import type { IconParameters } from "./types"; + +export { + IconParameters, + HomeIcon, + SettingsIcon, + PlusIcon, + MinusIcon, + DeviceIcon, + TrashIcon, + UpIcon, +}; diff --git a/src/components/icons/types.ts b/src/icons/types.ts similarity index 100% rename from src/components/icons/types.ts rename to src/icons/types.ts diff --git a/src/services/auth/auth.ts b/src/services/auth/auth.ts index c0d13f0..2344fca 100644 --- a/src/services/auth/auth.ts +++ b/src/services/auth/auth.ts @@ -1,6 +1,36 @@ -import { fetchLoginApi, fetchRefreshApi } from "../../api/auth"; -import { AuthActionTypes } from "../../store/auth"; -import { dispatch } from "../../store/state"; +import { + fetchRegisterApi, + fetchLoginApi, + fetchRefreshApi, +} from "../../api/auth"; +import { + setLoggedIn, + setRegisterSuccess, + setAuthSession, + setAuthSessionKey, + setAuthSessionIV, +} from "../../store/auth"; +import { state } from "../../store/state"; +import { hexToBytes } from "../crypto"; + +const fetchRegister = async ( + username: string, + password: string, + email: string, +) => { + const data = await fetchRegisterApi({ + username: username, + password: password, + email: email, + }); + + if (typeof data.error === "string") { + setRegisterSuccess(false); + return; + } + + setRegisterSuccess(true); +}; const fetchLogin = async (username: string, password: string) => { const data = await fetchLoginApi({ @@ -8,28 +38,42 @@ const fetchLogin = async (username: string, password: string) => { password: password, }); - dispatch({ - type: AuthActionTypes.FETCH_LOGIN_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } - dispatch({ - type: AuthActionTypes.FETCH_REFRESH_START, - }); + setAuthSession(data); + + fetchRefresh(); }; const fetchRefresh = async () => { const data = await fetchRefreshApi(); - dispatch({ - type: AuthActionTypes.FETCH_REFRESH_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + setLoggedIn(false); - dispatch({ - type: AuthActionTypes.SET_LOGGED_IN, - payload: "id" in data, - }); + return; + } + + setAuthSession(data); + setLoggedIn(true); + + if ( + !state.auth.session?.storageSecret || + state.auth.session.storageSecret.length !== 89 + ) { + return; + } + + const [keyHex, ivHex] = state.auth.session.storageSecret.split(";"); + const key = hexToBytes(keyHex); + const iv = hexToBytes(ivHex); + + if (key && iv) { + setAuthSessionKey(new Uint8Array(key)); + setAuthSessionIV(new Uint8Array(iv)); + } }; -export { fetchLogin, fetchRefresh }; +export { fetchRegister, fetchLogin, fetchRefresh }; diff --git a/src/services/channel/channel.ts b/src/services/channel/channel.ts index 0ac72dd..a3312b4 100644 --- a/src/services/channel/channel.ts +++ b/src/services/channel/channel.ts @@ -5,18 +5,24 @@ import { removeChannelApi, fetchChannelMessagesApi, } from "../../api/channel"; -import { ChannelActionTypes } from "../../store/channel"; -import { dispatch } from "../../store/state"; +import { + deleteChannel, + setChannel, + setChannelMessages, +} from "../../store/channel"; +import { IMessage } from "../../store/message"; +import { decryptMessage } from "../message"; const fetchChannel = async (id: string) => { const data = await fetchChannelApi({ id: id, }); - dispatch({ - type: ChannelActionTypes.FETCH_CHANNEL_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setChannel(data); }; const createChannel = async (name: string, communityId: string) => { @@ -25,10 +31,11 @@ const createChannel = async (name: string, communityId: string) => { communityId: communityId, }); - dispatch({ - type: ChannelActionTypes.CREATE_CHANNEL_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setChannel(data); }; const updateChannel = async ( @@ -42,10 +49,11 @@ const updateChannel = async ( description: description, }); - dispatch({ - type: ChannelActionTypes.UPDATE_CHANNEL_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setChannel(data); }; const removeChannel = async (id: string) => { @@ -53,21 +61,52 @@ const removeChannel = async (id: string) => { id: id, }); - dispatch({ - type: ChannelActionTypes.REMOVE_CHANNEL_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + deleteChannel(data.id); }; -const fetchChannelMessages = async (id: string) => { +const fetchChannelMessages = async (id: string, communityId: string) => { const data = await fetchChannelMessagesApi({ id: id, }); - dispatch({ - type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + const messages: IMessage[] = []; + + for (const message of data.messages) { + try { + const decrypted = await decryptMessage( + communityId, + message.text, + message.iv, + ); + if (decrypted) { + messages.push({ + ...message, + text: decrypted, + decryptionStatus: true, + }); + } else { + messages.push({ + ...message, + decryptionStatus: false, + }); + } + } catch { + messages.push({ + ...message, + decryptionStatus: false, + }); + } + } + + setChannelMessages(data.id, messages); }; export { diff --git a/src/services/community/community.ts b/src/services/community/community.ts index 65040c9..c1bf06c 100644 --- a/src/services/community/community.ts +++ b/src/services/community/community.ts @@ -8,22 +8,33 @@ import { fetchCommunityMembersApi, fetchCommunityInvitesApi, } from "../../api/community"; -import { CommunityActionTypes } from "../../store/community"; -import { ChannelActionTypes } from "../../store/channel"; -import { RoleActionTypes } from "../../store/role"; -import { UserActionTypes } from "../../store/user"; -import { dispatch, state } from "../../store/state"; -import { InviteActionTypes } from "../../store/invite"; +import { setChannel } from "../../store/channel"; +import { + deleteCommunity, + setCommunity, + setCommunityChannels, + setCommunityEncryptionKey, + setCommunityInvites, + setCommunityMembers, + setCommunityRoles, +} from "../../store/community"; +import { setInvite } from "../../store/invite"; +import { setRole } from "../../store/role"; +import { state } from "../../store/state"; +import { setUser } from "../../store/user"; +import { DB_STORE, dbLoadEncrypted } from "../database"; +import { fetchUserCommunities } from "../user"; const fetchCommunity = async (id: string) => { const data = await fetchCommunityApi({ id: id, }); - dispatch({ - type: CommunityActionTypes.FETCH_COMMUNITY_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setCommunity(data); }; const createCommunity = async (name: string) => { @@ -31,16 +42,14 @@ const createCommunity = async (name: string) => { name: name, }); - dispatch({ - type: CommunityActionTypes.CREATE_COMMUNITY_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setCommunity(data); if (state.user.loggedUserId) { - dispatch({ - type: UserActionTypes.FETCH_USER_COMMUNITIES_START, - payload: state.user.loggedUserId, - }); + fetchUserCommunities(state.user.loggedUserId); } }; @@ -55,16 +64,14 @@ const updateCommunity = async ( description: description, }); - dispatch({ - type: CommunityActionTypes.UPDATE_COMMUNITY_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setCommunity(data); if (state.user.loggedUserId) { - dispatch({ - type: UserActionTypes.FETCH_USER_COMMUNITIES_START, - payload: state.user.loggedUserId, - }); + fetchUserCommunities(state.user.loggedUserId); } }; @@ -73,16 +80,14 @@ const removeCommunity = async (id: string) => { id: id, }); - dispatch({ - type: CommunityActionTypes.REMOVE_COMMUNITY_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + deleteCommunity(data.id); if (state.user.loggedUserId) { - dispatch({ - type: UserActionTypes.FETCH_USER_COMMUNITIES_START, - payload: state.user.loggedUserId, - }); + fetchUserCommunities(state.user.loggedUserId); } }; @@ -91,16 +96,14 @@ const fetchCommunityChannels = async (id: string) => { id: id, }); - dispatch({ - type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setCommunityChannels(data.id, data.channels); data.channels.forEach((channel) => { - dispatch({ - type: ChannelActionTypes.SET_CHANNEL, - payload: channel, - }); + setChannel(channel); }); }; @@ -109,16 +112,14 @@ const fetchCommunityRoles = async (id: string) => { id: id, }); - dispatch({ - type: CommunityActionTypes.FETCH_COMMUNITY_ROLES_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setCommunityRoles(data.id, data.roles); data.roles.forEach((role) => { - dispatch({ - type: RoleActionTypes.SET_ROLE, - payload: role, - }); + setRole(role); }); }; @@ -127,16 +128,14 @@ const fetchCommunityMembers = async (id: string) => { id: id, }); - dispatch({ - type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setCommunityMembers(data.id, data.members); data.members.forEach((member) => { - dispatch({ - type: UserActionTypes.SET_USER, - payload: member, - }); + setUser(member); }); }; @@ -145,15 +144,34 @@ const fetchCommunityInvites = async (id: string) => { id: id, }); - dispatch({ - type: CommunityActionTypes.FETCH_COMMUNITY_INVITES_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setCommunityInvites(data.id, data.invites); data.invites.forEach((invite) => { - dispatch({ - type: InviteActionTypes.SET_INVITE, - payload: invite, + setInvite(invite); + }); +}; + +const loadCommunityCryptoStates = async () => { + if (!state.user.loggedUserId) { + return; + } + const communities = state.user.users[state.user.loggedUserId]?.communities; + if (!communities) { + return; + } + + communities.forEach((communityId) => { + dbLoadEncrypted( + DB_STORE.COMMUNITY_ENCRYPTION_KEYS, + communityId, + ).then((communityEncryptionKey) => { + if (communityEncryptionKey) { + setCommunityEncryptionKey(communityId, communityEncryptionKey); + } }); }); }; @@ -167,4 +185,5 @@ export { fetchCommunityRoles, fetchCommunityMembers, fetchCommunityInvites, + loadCommunityCryptoStates, }; diff --git a/src/services/crypto/crypto.ts b/src/services/crypto/crypto.ts new file mode 100644 index 0000000..a0671d4 --- /dev/null +++ b/src/services/crypto/crypto.ts @@ -0,0 +1,95 @@ +import { ICryptoEncrypted, ICryptoData } from "./types"; + +const importKey = async (key: Uint8Array): Promise => { + return await crypto.subtle.importKey( + "raw", + key, + { name: "AES-GCM" }, + false, + ["encrypt", "decrypt"], + ); +}; + +const deriveKey = async (password: string): Promise => { + const salt = "NEXLINK_FIXED_SALT"; + const encoder = new TextEncoder(); + + const passwordKey = await crypto.subtle.importKey( + "raw", + encoder.encode(password), + "PBKDF2", + false, + ["deriveKey"], + ); + + return crypto.subtle.deriveKey( + { + name: "PBKDF2", + salt: encoder.encode(salt), + iterations: 100000, + hash: "SHA-256", + }, + passwordKey, + { name: "AES-GCM", length: 256 }, + true, + ["encrypt", "decrypt"], + ); +}; + +const encryptData = async ( + cryptoData: ICryptoData, +): Promise => { + const encoded = new TextEncoder().encode(JSON.stringify(cryptoData.data)); + const encrypted = await crypto.subtle.encrypt( + { name: "AES-GCM", iv: cryptoData.iv }, + cryptoData.key, + encoded, + ); + + return encrypted; +}; + +const decryptData = async (cryptoData: ICryptoEncrypted): Promise => { + const decrypted = await crypto.subtle.decrypt( + { name: "AES-GCM", iv: cryptoData.iv }, + cryptoData.key, + cryptoData.encryptedData, + ); + const decoded = JSON.parse(new TextDecoder().decode(decrypted)); + + return decoded as T; +}; + +const generateIv = (): Uint8Array => { + return crypto.getRandomValues(new Uint8Array(12)); +}; + +const hexToBytes = (hex: string): ArrayBuffer | undefined => { + if (hex.length % 2 !== 0) { + return; + } + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i++) { + bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return bytes.buffer; +}; + +const bytesToHex = (bytes: ArrayBuffer): string => { + const bytesUint8 = new Uint8Array(bytes); + let hex = ""; + for (const byte of bytesUint8) { + hex += byte.toString(16).padStart(2, "0"); + } + return hex; +}; + +export { + importKey, + deriveKey, + encryptData, + decryptData, + generateIv, + hexToBytes, + bytesToHex, +}; diff --git a/src/services/crypto/index.ts b/src/services/crypto/index.ts new file mode 100644 index 0000000..4d929f8 --- /dev/null +++ b/src/services/crypto/index.ts @@ -0,0 +1 @@ +export * from "./crypto"; diff --git a/src/services/crypto/types.ts b/src/services/crypto/types.ts new file mode 100644 index 0000000..81db159 --- /dev/null +++ b/src/services/crypto/types.ts @@ -0,0 +1,13 @@ +interface ICryptoEncrypted { + key: CryptoKey; + iv: Uint8Array; + encryptedData: ArrayBuffer; +} + +interface ICryptoData { + key: CryptoKey; + iv: Uint8Array; + data: T; +} + +export { type ICryptoEncrypted, type ICryptoData }; diff --git a/src/services/database/database.ts b/src/services/database/database.ts new file mode 100644 index 0000000..71b6008 --- /dev/null +++ b/src/services/database/database.ts @@ -0,0 +1,114 @@ +import { state } from "../../store/state"; +import { decryptData, encryptData, importKey } from "../crypto"; +import { DB_STORE, IDatabaseItem } from "./types"; + +const DB_VERSION = 1; + +const openDB = async (name: string, store: string): Promise => { + return new Promise((resolve, reject) => { + const request = indexedDB.open(name, DB_VERSION); + + request.onupgradeneeded = () => { + const db = request.result; + + if ( + !db.objectStoreNames.contains( + DB_STORE.COMMUNITY_ENCRYPTION_KEYS, + ) + ) { + db.createObjectStore(DB_STORE.COMMUNITY_ENCRYPTION_KEYS, { + keyPath: "id", + }); + } + }; + + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); +}; + +const getDB = async (store: string): Promise => { + return await openDB("pulsardb", store); +}; + +const dbSave = async (store: string, item: IDatabaseItem): Promise => { + const db = await getDB(store); + + return new Promise((resolve, reject) => { + const tx = db.transaction(store, "readwrite"); + tx.objectStore(store).put(item); + + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); +}; + +const dbLoad = async ( + store: string, + id: IDBValidKey, +): Promise => { + const db = await getDB(store); + + return new Promise((resolve, reject) => { + const tx = db.transaction(store, "readonly"); + const req = tx.objectStore(store).get(id); + + req.onsuccess = () => resolve(req.result as IDatabaseItem | undefined); + req.onerror = () => reject(req.error); + }); +}; + +const dbSaveEncrypted = async (store: string, id: IDBValidKey, item: T) => { + const key = state.auth.session?.key; + const iv = state.auth.session?.iv; + if (!key || !iv) { + return; + } + + const importedKey = await importKey(key); + const encrypted = await encryptData({ + key: importedKey, + iv: iv, + data: item, + }); + + await dbSave(store, { + id: id, + data: encrypted, + }); +}; + +const dbLoadEncrypted = async ( + store: string, + id: IDBValidKey, +): Promise => { + const key = state.auth.session?.key; + const iv = state.auth.session?.iv; + if (!key || !iv) { + return; + } + + const item = await dbLoad(store, id); + if (!item) { + return; + } + + const importedKey = await importKey(key); + const decrypted = await decryptData({ + key: importedKey, + iv: iv, + encryptedData: item.data, + }); + + return decrypted; +}; + +export { + openDB, + getDB, + dbSave, + dbLoad, + importKey, + dbSaveEncrypted, + dbLoadEncrypted, +}; diff --git a/src/services/database/index.ts b/src/services/database/index.ts new file mode 100644 index 0000000..777f981 --- /dev/null +++ b/src/services/database/index.ts @@ -0,0 +1,2 @@ +export * from "./database"; +export * from "./types"; diff --git a/src/services/database/types.ts b/src/services/database/types.ts new file mode 100644 index 0000000..ad4ab54 --- /dev/null +++ b/src/services/database/types.ts @@ -0,0 +1,10 @@ +enum DB_STORE { + COMMUNITY_ENCRYPTION_KEYS = "COMMUNITY_ENCRYPTION_KEYS", +} + +interface IDatabaseItem { + id: IDBValidKey; + data: ArrayBuffer; +} + +export { DB_STORE, type IDatabaseItem }; diff --git a/src/services/invite/invite.ts b/src/services/invite/invite.ts index 26a6f87..578a893 100644 --- a/src/services/invite/invite.ts +++ b/src/services/invite/invite.ts @@ -3,19 +3,20 @@ import { removeInviteApi, acceptInviteApi, } from "../../api/invite"; -import { InviteActionTypes } from "../../store/invite"; -import { dispatch, state } from "../../store/state"; -import { UserActionTypes } from "../../store/user"; +import { deleteInvite, setInvite } from "../../store/invite"; +import { state } from "../../store/state"; +import { fetchUserCommunities } from "../user"; const fetchInvite = async (id: string) => { const data = await fetchInviteApi({ id: id, }); - dispatch({ - type: InviteActionTypes.FETCH_INVITE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setInvite(data); }; const removeInvite = async (id: string) => { @@ -23,10 +24,11 @@ const removeInvite = async (id: string) => { id: id, }); - dispatch({ - type: InviteActionTypes.REMOVE_INVITE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + deleteInvite(data.id); }; const acceptInvite = async (id: string) => { @@ -34,16 +36,12 @@ const acceptInvite = async (id: string) => { id: id, }); - dispatch({ - type: InviteActionTypes.ACCEPT_INVITE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } if (state.user.loggedUserId) { - dispatch({ - type: UserActionTypes.FETCH_USER_COMMUNITIES_START, - payload: state.user.loggedUserId, - }); + fetchUserCommunities(state.user.loggedUserId); } }; diff --git a/src/services/message/message.ts b/src/services/message/message.ts index 2f77422..5a1a492 100644 --- a/src/services/message/message.ts +++ b/src/services/message/message.ts @@ -4,29 +4,62 @@ import { updateMessageApi, removeMessageApi, } from "../../api/message"; -import { MessageActionTypes } from "../../store/message"; -import { dispatch } from "../../store/state"; +import { deleteMessage, setMessage } from "../../store/message"; +import { state } from "../../store/state"; +import { + bytesToHex, + decryptData, + encryptData, + generateIv, + hexToBytes, +} from "../crypto"; -const fetchMessage = async (id: string) => { +const fetchMessage = async (id: string, communityId: string) => { const data = await fetchMessageApi({ id: id, }); - dispatch({ - type: MessageActionTypes.FETCH_MESSAGE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + try { + const decrypted = await decryptMessage(communityId, data.text, data.iv); + if (!decrypted) { + return; + } + + setMessage({ + ...data, + text: decrypted, + }); + } catch {} }; -const createMessage = async (text: string, channelId: string) => { +const createMessage = async ( + communityId: string, + channelId: string, + text: string, +) => { + const encrypted = await encryptMessage(communityId, text); + if (!encrypted) { + return; + } + const [encryptedMessage, iv] = encrypted; + const data = await createMessageApi({ - text: text, channelId: channelId, + iv: iv, + text: encryptedMessage, }); - dispatch({ - type: MessageActionTypes.CREATE_MESSAGE_FINISH, - payload: data, + if (typeof data.error === "string") { + return; + } + + setMessage({ + ...data, + text: text, }); }; @@ -36,10 +69,11 @@ const updateMessage = async (id: string, text: string) => { text: text, }); - dispatch({ - type: MessageActionTypes.UPDATE_MESSAGE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setMessage(data); }; const removeMessage = async (id: string) => { @@ -47,10 +81,60 @@ const removeMessage = async (id: string) => { id: id, }); - dispatch({ - type: MessageActionTypes.REMOVE_MESSAGE_FINISH, - payload: data, + if (typeof data.error === "string") { + return; + } + + deleteMessage(); +}; + +const encryptMessage = async ( + communityId: string, + text: string, +): Promise<[string, string] | undefined> => { + const key = state.community.communities[communityId]?.derivedKey; + if (!key) { + return; + } + + const iv = generateIv(); + const encrypted = await encryptData({ + key: key, + iv: iv, + data: text, + }); + + return [bytesToHex(encrypted), bytesToHex(iv.buffer)]; +}; + +const decryptMessage = async ( + communityId: string, + text: string, + iv: string, +): Promise => { + const key = state.community.communities[communityId]?.derivedKey; + if (!key) { + return; + } + + const ivBytes = hexToBytes(iv); + const textBytes = hexToBytes(text); + if (!ivBytes || !textBytes) { + return; + } + + return await decryptData({ + key: key, + iv: new Uint8Array(ivBytes), + encryptedData: textBytes, }); }; -export { fetchMessage, createMessage, updateMessage, removeMessage }; +export { + fetchMessage, + createMessage, + updateMessage, + removeMessage, + encryptMessage, + decryptMessage, +}; diff --git a/src/services/role/role.ts b/src/services/role/role.ts index aa7d706..afd64de 100644 --- a/src/services/role/role.ts +++ b/src/services/role/role.ts @@ -4,18 +4,18 @@ import { updateRoleApi, removeRoleApi, } from "../../api/role"; -import { RoleActionTypes } from "../../store/role"; -import { dispatch } from "../../store/state"; +import { deleteRole, setRole } from "../../store/role"; const fetchRole = async (id: string) => { const data = await fetchRoleApi({ id: id, }); - dispatch({ - type: RoleActionTypes.FETCH_ROLE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setRole(data); }; const createRole = async (name: string, communityId: string) => { @@ -24,10 +24,11 @@ const createRole = async (name: string, communityId: string) => { communityId: communityId, }); - dispatch({ - type: RoleActionTypes.CREATE_ROLE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setRole(data); }; const updateRole = async (id: string, name?: string) => { @@ -36,10 +37,11 @@ const updateRole = async (id: string, name?: string) => { name: name, }); - dispatch({ - type: RoleActionTypes.UPDATE_ROLE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setRole(data); }; const removeRole = async (id: string) => { @@ -47,10 +49,11 @@ const removeRole = async (id: string) => { id: id, }); - dispatch({ - type: RoleActionTypes.REMOVE_ROLE_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + deleteRole(data.id); }; export { fetchRole, createRole, updateRole, removeRole }; diff --git a/src/services/session/session.ts b/src/services/session/session.ts index 6c319ac..ebbe5f8 100644 --- a/src/services/session/session.ts +++ b/src/services/session/session.ts @@ -1,16 +1,16 @@ import { fetchSessionApi, removeSessionApi } from "../../api/session"; -import { SessionActionTypes } from "../../store/session"; -import { dispatch } from "../../store/state"; +import { deleteSession, setSession } from "../../store/session"; const fetchSession = async (id: string) => { const data = await fetchSessionApi({ id: id, }); - dispatch({ - type: SessionActionTypes.FETCH_SESSION_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setSession(data); }; const removeSession = async (id: string) => { @@ -18,10 +18,11 @@ const removeSession = async (id: string) => { id: id, }); - dispatch({ - type: SessionActionTypes.REMOVE_SESSION_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + deleteSession(data.id); }; export { fetchSession, removeSession }; diff --git a/src/services/user/user.ts b/src/services/user/user.ts index f260f8f..078df2d 100644 --- a/src/services/user/user.ts +++ b/src/services/user/user.ts @@ -4,18 +4,23 @@ import { fetchUserSessionsApi, fetchUserCommunitiesApi, } from "../../api/user"; -import { UserActionTypes } from "../../store/user"; -import { CommunityActionTypes } from "../../store/community"; -import { dispatch } from "../../store/state"; -import { SessionActionTypes } from "../../store/session"; +import { setCommunity } from "../../store/community"; +import { setSession } from "../../store/session"; +import { + setLoggedUserId, + setUser, + setUserCommunities, + setUserSessions, +} from "../../store/user"; const fetchLoggedUser = async () => { const data = await fetchLoggedUserApi(); - dispatch({ - type: UserActionTypes.FETCH_LOGGED_USER_ID_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setLoggedUserId(data.id); }; const fetchUser = async (id: string) => { @@ -23,10 +28,11 @@ const fetchUser = async (id: string) => { id: id, }); - dispatch({ - type: UserActionTypes.FETCH_USER_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setUser(data); }; const fetchUserSessions = async (id: string) => { @@ -34,16 +40,14 @@ const fetchUserSessions = async (id: string) => { id: id, }); - dispatch({ - type: UserActionTypes.FETCH_USER_SESSIONS_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setUserSessions(data); data.sessions.forEach((session) => { - dispatch({ - type: SessionActionTypes.SET_SESSION, - payload: session, - }); + setSession(session); }); }; @@ -52,16 +56,14 @@ const fetchUserCommunities = async (id: string) => { id: id, }); - dispatch({ - type: UserActionTypes.FETCH_USER_COMMUNITIES_FINISH, - payload: data, - }); + if (typeof data.error === "string") { + return; + } + + setUserCommunities(data); data.communities.forEach((community) => { - dispatch({ - type: CommunityActionTypes.SET_COMMUNITY, - payload: community, - }); + setCommunity(community); }); }; diff --git a/src/services/websocket/types.ts b/src/services/websocket/types.ts index e79a5c1..d3ea145 100644 --- a/src/services/websocket/types.ts +++ b/src/services/websocket/types.ts @@ -5,9 +5,12 @@ enum SocketRequestTypes { } enum SocketMessageTypes { - NEW_ANNOUNCEMENT = "NEW_ANNOUNCEMENT", - NEW_MESSAGE = "NEW_MESSAGE", - NEW_CHANNEL = "NEW_CHANNEL", + ANNOUNCEMENT = "ANNOUNCEMENT", + SET_MESSAGE = "SET_MESSAGE", + DELETE_MESSAGE = "DELETE_MESSAGE", + UPDATE_CHANNELS = "UPDATE_CHANNELS", + UPDATE_ROLES = "UPDATE_ROLES", + UPDATE_MEMBERS = "UPDATE_MEMBERS", } type SocketRequest = { @@ -16,25 +19,42 @@ type SocketRequest = { type SocketMessage = | { - type: SocketMessageTypes.NEW_ANNOUNCEMENT; + type: SocketMessageTypes.ANNOUNCEMENT; payload: { title: string; description: string; }; } | { - type: SocketMessageTypes.NEW_MESSAGE; + type: SocketMessageTypes.SET_MESSAGE; payload: { channelId: string; message: IFetchChannelMessage; }; } | { - type: SocketMessageTypes.NEW_CHANNEL; + type: SocketMessageTypes.DELETE_MESSAGE; + payload: { + channelId: string; + messageId: string; + }; + } + | { + type: SocketMessageTypes.UPDATE_CHANNELS; + payload: { + communityId: string; + }; + } + | { + type: SocketMessageTypes.UPDATE_ROLES; + payload: { + communityId: string; + }; + } + | { + type: SocketMessageTypes.UPDATE_MEMBERS; payload: { - id: string; communityId: string; - name: string; }; }; diff --git a/src/services/websocket/websocket.ts b/src/services/websocket/websocket.ts index fca7251..779a02a 100644 --- a/src/services/websocket/websocket.ts +++ b/src/services/websocket/websocket.ts @@ -1,5 +1,11 @@ -import { ChannelActionTypes } from "../../store/channel"; -import { dispatch } from "../../store/state"; +import { deleteChannelMessage, setChannelMessage } from "../../store/channel"; +import { state } from "../../store/state"; +import { + fetchCommunityChannels, + fetchCommunityMembers, + fetchCommunityRoles, +} from "../community"; +import { decryptMessage } from "../message"; import config from "./config.json"; import { SocketMessage, SocketMessageTypes } from "./types"; @@ -38,18 +44,62 @@ const parseMessage = (data: string): SocketMessage | null => { const handleMessage = (message: SocketMessage) => { switch (message.type) { - case SocketMessageTypes.NEW_MESSAGE: { - dispatch({ - type: ChannelActionTypes.SET_CHANNEL_MESSAGE, - payload: { - id: message.payload.channelId, - message: message.payload.message, - }, - }); + case SocketMessageTypes.SET_MESSAGE: { + try { + const communityId = + state.channel.channels[message.payload.channelId] + ?.communityId; + if (!communityId) { + break; + } + decryptMessage( + communityId, + message.payload.message.text, + message.payload.message.iv, + ).then((decrypted) => { + if (decrypted) { + setChannelMessage(message.payload.channelId, { + ...message.payload.message, + text: decrypted, + decryptionStatus: true, + }); + } else { + setChannelMessage(message.payload.channelId, { + ...message.payload.message, + decryptionStatus: false, + }); + } + }); + } catch { + setChannelMessage(message.payload.channelId, { + ...message.payload.message, + decryptionStatus: false, + }); + } + + break; } - case SocketMessageTypes.NEW_CHANNEL: { + case SocketMessageTypes.DELETE_MESSAGE: { + deleteChannelMessage( + message.payload.channelId, + message.payload.messageId, + ); + break; } - case SocketMessageTypes.NEW_ANNOUNCEMENT: { + case SocketMessageTypes.UPDATE_CHANNELS: { + fetchCommunityChannels(message.payload.communityId); + break; + } + case SocketMessageTypes.UPDATE_ROLES: { + fetchCommunityRoles(message.payload.communityId); + break; + } + case SocketMessageTypes.UPDATE_MEMBERS: { + fetchCommunityMembers(message.payload.communityId); + break; + } + case SocketMessageTypes.ANNOUNCEMENT: { + break; } } }; diff --git a/src/store/actions.ts b/src/store/actions.ts deleted file mode 100644 index 51769c9..0000000 --- a/src/store/actions.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AppActionTypes, AppAction } from "./app"; -import { AuthActionTypes, AuthAction } from "./auth"; -import { UserActionTypes, UserAction } from "./user"; -import { CommunityActionTypes, CommunityAction } from "./community"; -import { ChannelActionTypes, ChannelAction } from "./channel"; -import { RoleActionTypes, RoleAction } from "./role"; -import { SessionActionTypes, SessionAction } from "./session"; -import { InviteActionTypes, InviteAction } from "./invite"; -import { MessageActionTypes, MessageAction } from "./message"; - -type ActionTypes = - | AppActionTypes - | AuthActionTypes - | UserActionTypes - | CommunityActionTypes - | ChannelActionTypes - | RoleActionTypes - | SessionActionTypes - | InviteActionTypes - | MessageActionTypes; - -type Action = - | AppAction - | AuthAction - | UserAction - | CommunityAction - | ChannelAction - | RoleAction - | SessionAction - | InviteAction - | MessageAction; - -export { type Action, type ActionTypes }; diff --git a/src/store/app/actions.ts b/src/store/app/actions.ts deleted file mode 100644 index 9525b14..0000000 --- a/src/store/app/actions.ts +++ /dev/null @@ -1,17 +0,0 @@ -enum AppActionTypes { - SET_HOME_OPEN = "SET_HOME_OPEN", - SET_SETTINGS_OPEN = "SET_SETTINGS_OPEN", - SET_ADD_COMMUNITY_OPEN = "SET_ADD_COMMUNITY_OPEN", - SET_ADD_COMMUNITY_SETTINGS_OPEN = "SET_ADD_COMMUNITY_SETTINGS_OPEN", -} - -type AppAction = - | { type: AppActionTypes.SET_HOME_OPEN; payload: boolean } - | { type: AppActionTypes.SET_SETTINGS_OPEN; payload: boolean } - | { type: AppActionTypes.SET_ADD_COMMUNITY_OPEN; payload: boolean } - | { - type: AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN; - payload: boolean; - }; - -export { AppActionTypes, type AppAction }; diff --git a/src/store/app/app.ts b/src/store/app/app.ts index b9956ef..970fc20 100644 --- a/src/store/app/app.ts +++ b/src/store/app/app.ts @@ -1,30 +1,27 @@ import { setState } from "../state"; -import { AppActionTypes, AppAction } from "./actions"; -import { IAppState } from "./types"; -function appReducer(_state: IAppState, action: AppAction) { - switch (action.type) { - case AppActionTypes.SET_HOME_OPEN: - setState("app", "homeOpen", action.payload); - break; - case AppActionTypes.SET_SETTINGS_OPEN: - setState("app", "dialogsOpen", "settingsOpen", false); - setState("app", "dialogsOpen", "settingsOpen", action.payload); - break; - case AppActionTypes.SET_ADD_COMMUNITY_OPEN: - setState("app", "dialogsOpen", "addCommunityOpen", false); - setState("app", "dialogsOpen", "addCommunityOpen", action.payload); - break; - case AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN: - setState("app", "dialogsOpen", "communitySettingsOpen", false); - setState( - "app", - "dialogsOpen", - "communitySettingsOpen", - action.payload, - ); - break; - } -} +const setHomeOpen = (value: boolean) => { + setState("app", "homeOpen", value); +}; -export { appReducer }; +const setSettingsOpen = (value: boolean) => { + setState("app", "dialogsOpen", "settingsOpen", false); + setState("app", "dialogsOpen", "settingsOpen", value); +}; + +const setAddCommunityOpen = (value: boolean) => { + setState("app", "dialogsOpen", "addCommunityOpen", false); + setState("app", "dialogsOpen", "addCommunityOpen", value); +}; + +const setCommunitySettingsOpen = (value: boolean) => { + setState("app", "dialogsOpen", "communitySettingsOpen", false); + setState("app", "dialogsOpen", "communitySettingsOpen", value); +}; + +export { + setHomeOpen, + setSettingsOpen, + setAddCommunityOpen, + setCommunitySettingsOpen, +}; diff --git a/src/store/app/index.ts b/src/store/app/index.ts index c516023..77dda35 100644 --- a/src/store/app/index.ts +++ b/src/store/app/index.ts @@ -1,3 +1,2 @@ export * from "./app"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/auth/actions.ts b/src/store/auth/actions.ts deleted file mode 100644 index 9e0e4d6..0000000 --- a/src/store/auth/actions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - IFetchLoginRequest, - IFetchLoginResponse, - IFetchRefreshResponseError, - IFetchRefreshResponseSuccess, -} from "../../api/auth"; - -enum AuthActionTypes { - SET_LOGGED_IN = "SET_LOGGED_IN", - FETCH_LOGIN_START = "FETCH_LOGIN_START", - FETCH_LOGIN_FINISH = "FETCH_LOGIN_FINISH", - FETCH_REFRESH_START = "FETCH_REFRESH_START", - FETCH_REFRESH_FINISH = "FETCH_REFRESH_FINISH", -} - -type AuthAction = - | { type: AuthActionTypes.SET_LOGGED_IN; payload: boolean } - | { type: AuthActionTypes.FETCH_LOGIN_START; payload: IFetchLoginRequest } - | { - type: AuthActionTypes.FETCH_LOGIN_FINISH; - payload: IFetchLoginResponse; - } - | { type: AuthActionTypes.FETCH_REFRESH_START } - | { - type: AuthActionTypes.FETCH_REFRESH_FINISH; - payload: IFetchRefreshResponseError | IFetchRefreshResponseSuccess; - }; - -export { AuthActionTypes, type AuthAction }; diff --git a/src/store/auth/auth.ts b/src/store/auth/auth.ts index 14685cf..907fc32 100644 --- a/src/store/auth/auth.ts +++ b/src/store/auth/auth.ts @@ -1,28 +1,45 @@ -import { fetchLogin, fetchRefresh } from "../../services/auth"; import { setState } from "../state"; -import { AuthActionTypes, AuthAction } from "./actions"; -import { IAuthState } from "./types"; +import { IAuthSession } from "./types"; -function authReducer(_state: IAuthState, action: AuthAction) { - switch (action.type) { - case AuthActionTypes.SET_LOGGED_IN: - setState("auth", "loggedIn", action.payload); - break; - case AuthActionTypes.FETCH_LOGIN_START: - fetchLogin(action.payload.username, action.payload.password); - break; - case AuthActionTypes.FETCH_LOGIN_FINISH: - setState("auth", "session", action.payload); - break; - case AuthActionTypes.FETCH_REFRESH_START: - fetchRefresh(); - break; - case AuthActionTypes.FETCH_REFRESH_FINISH: - if ("id" in action.payload) { - setState("auth", "session", action.payload); - } - break; - } -} +const setRegisterSuccess = (value: boolean) => { + setState("auth", "registerSuccess", value); +}; -export { authReducer }; +const resetRegisterSuccess = () => { + setState("auth", "registerSuccess", undefined); +}; + +const setLoggedIn = (value: boolean) => { + setState("auth", "loggedIn", value); +}; + +const resetLoggedIn = () => { + setState("auth", "loggedIn", undefined); +}; + +const setAuthSession = (session: IAuthSession) => { + setState("auth", "session", session); +}; + +const resetAuthSession = () => { + setState("auth", "session", undefined); +}; + +const setAuthSessionKey = (key: Uint8Array) => { + setState("auth", "session", "key", key); +}; + +const setAuthSessionIV = (iv: Uint8Array) => { + setState("auth", "session", "iv", iv); +}; + +export { + setRegisterSuccess, + resetRegisterSuccess, + setLoggedIn, + resetLoggedIn, + setAuthSession, + resetAuthSession, + setAuthSessionKey, + setAuthSessionIV, +}; diff --git a/src/store/auth/index.ts b/src/store/auth/index.ts index f821cc3..130045b 100644 --- a/src/store/auth/index.ts +++ b/src/store/auth/index.ts @@ -1,3 +1,2 @@ export * from "./auth"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/auth/types.ts b/src/store/auth/types.ts index a57272f..9d37ac4 100644 --- a/src/store/auth/types.ts +++ b/src/store/auth/types.ts @@ -1,12 +1,16 @@ interface IAuthState { + registerSuccess?: boolean; loggedIn?: boolean; - session?: ISession; + session?: IAuthSession; } -interface ISession { +interface IAuthSession { id?: string; ownerId?: string; token?: string; + storageSecret?: string; + key?: Uint8Array; + iv?: Uint8Array; } -export { type IAuthState, type ISession }; +export { type IAuthState, type IAuthSession }; diff --git a/src/store/channel/actions.ts b/src/store/channel/actions.ts deleted file mode 100644 index c6b97cb..0000000 --- a/src/store/channel/actions.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - ICreateChannelRequest, - IUpdateChannelRequest, - IFetchChannelResponse, - ICreateChannelResponse, - IUpdateChannelResponse, - IRemoveChannelResponse, - IFetchChannelMessagesResponse, - IFetchChannelMessage, -} from "../../api/channel"; -import { IFetchCommunityChannel } from "../../api/community"; - -enum ChannelActionTypes { - SET_CHANNEL = "SET_CHANNEL", - SET_ACTIVE_CHANNEL = "SET_ACTIVE_CHANNEL", - SET_TEXT = "SET_TEXT", - SET_CHANNEL_MESSAGE = "SET_CHANNEL_MESSAGE", - FETCH_CHANNEL_START = "FETCH_CHANNEL_START", - FETCH_CHANNEL_FINISH = "FETCH_CHANNEL_FINISH", - CREATE_CHANNEL_START = "CREATE_CHANNEL_START", - CREATE_CHANNEL_FINISH = "CREATE_CHANNEL_FINISH", - UPDATE_CHANNEL_START = "UPDATE_CHANNEL_START", - UPDATE_CHANNEL_FINISH = "UPDATE_CHANNEL_FINISH", - REMOVE_CHANNEL_START = "REMOVE_CHANNEL_START", - REMOVE_CHANNEL_FINISH = "REMOVE_CHANNEL_FINISH", - FETCH_CHANNEL_MESSAGES_START = "FETCH_CHANNEL_MESSAGES_START", - FETCH_CHANNEL_MESSAGES_FINISH = "FETCH_CHANNEL_MESSAGES_FINISH", -} - -type ChannelAction = - | { - type: ChannelActionTypes.SET_CHANNEL; - payload: IFetchCommunityChannel; - } - | { - type: ChannelActionTypes.SET_ACTIVE_CHANNEL; - payload: string | undefined; - } - | { - type: ChannelActionTypes.SET_TEXT; - payload: { id: string; text: string }; - } - | { - type: ChannelActionTypes.SET_CHANNEL_MESSAGE; - payload: { id: string; message: IFetchChannelMessage }; - } - | { type: ChannelActionTypes.FETCH_CHANNEL_START; payload: string } - | { - type: ChannelActionTypes.FETCH_CHANNEL_FINISH; - payload: IFetchChannelResponse; - } - | { - type: ChannelActionTypes.CREATE_CHANNEL_START; - payload: ICreateChannelRequest; - } - | { - type: ChannelActionTypes.CREATE_CHANNEL_FINISH; - payload: ICreateChannelResponse; - } - | { - type: ChannelActionTypes.UPDATE_CHANNEL_START; - payload: IUpdateChannelRequest; - } - | { - type: ChannelActionTypes.UPDATE_CHANNEL_FINISH; - payload: IUpdateChannelResponse; - } - | { type: ChannelActionTypes.REMOVE_CHANNEL_START; payload: string } - | { - type: ChannelActionTypes.REMOVE_CHANNEL_FINISH; - payload: IRemoveChannelResponse; - } - | { type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_START; payload: string } - | { - type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_FINISH; - payload: IFetchChannelMessagesResponse; - }; - -export { ChannelActionTypes, type ChannelAction }; diff --git a/src/store/channel/channel.ts b/src/store/channel/channel.ts index 3205b6e..5a58ed2 100644 --- a/src/store/channel/channel.ts +++ b/src/store/channel/channel.ts @@ -1,93 +1,60 @@ -import { - fetchChannel, - createChannel, - updateChannel, - removeChannel, - fetchChannelMessages, -} from "../../services/channel"; +import { IMessage } from "../message"; import { setState } from "../state"; -import { ChannelActionTypes, ChannelAction } from "./actions"; -import { IChannelState } from "./types"; +import { IChannel } from "./types"; -function channelReducer(_state: IChannelState, action: ChannelAction) { - switch (action.type) { - case ChannelActionTypes.SET_CHANNEL: - setState("channel", "channels", action.payload.id, action.payload); - break; - case ChannelActionTypes.SET_ACTIVE_CHANNEL: - setState("channel", "active", action.payload); - break; - case ChannelActionTypes.SET_TEXT: - setState( - "channel", - "channels", - action.payload.id, - "text", - action.payload.text, - ); - break; - case ChannelActionTypes.SET_CHANNEL_MESSAGE: - setState( - "channel", - "channels", - action.payload.id, - "messages", - action.payload.message.id, - action.payload.message, - ); - break; - case ChannelActionTypes.FETCH_CHANNEL_START: - fetchChannel(action.payload); - break; - case ChannelActionTypes.FETCH_CHANNEL_FINISH: - setState("channel", "channels", action.payload.id, action.payload); - break; - case ChannelActionTypes.CREATE_CHANNEL_START: - createChannel(action.payload.name, action.payload.communityId); - break; - case ChannelActionTypes.CREATE_CHANNEL_FINISH: - setState("channel", "channels", action.payload.id, action.payload); - break; - case ChannelActionTypes.UPDATE_CHANNEL_START: - updateChannel( - action.payload.id, - action.payload.name, - action.payload.description, - ); - break; - case ChannelActionTypes.UPDATE_CHANNEL_FINISH: - setState("channel", "channels", action.payload.id, action.payload); - break; - case ChannelActionTypes.REMOVE_CHANNEL_START: - removeChannel(action.payload); - break; - case ChannelActionTypes.REMOVE_CHANNEL_FINISH: - setState("channel", "channels", (channels) => { - const copy = { ...channels }; - delete copy[action.payload.id]; - return copy; - }); - break; - case ChannelActionTypes.FETCH_CHANNEL_MESSAGES_START: - fetchChannelMessages(action.payload); - break; - case ChannelActionTypes.FETCH_CHANNEL_MESSAGES_FINISH: - setState( - "channel", - "channels", - action.payload.id, - "messages", - (messages) => { - const newMessages = Object.fromEntries( - action.payload.messages.map((item) => [item.id, item]), - ); +const setChannel = (channel: IChannel) => { + setState("channel", "channels", channel.id, channel); +}; - const copy = { ...messages, ...newMessages }; - return copy; - }, - ); - break; - } -} +const deleteChannel = (channelId: string) => { + setState("channel", "channels", channelId, undefined); +}; -export { channelReducer }; +const setActiveChannel = (channelId: string) => { + setState("channel", "active", channelId); +}; + +const resetActiveChannel = () => { + setState("channel", "active", undefined); +}; + +const setText = (channelId: string, text: string) => { + setState("channel", "channels", channelId, "text", text); +}; + +const setChannelMessage = (channelId: string, message: IMessage) => { + setState("channel", "channels", channelId, "messages", message.id, message); +}; + +const setChannelMessages = (channelId: string, addMessages: IMessage[]) => { + setState("channel", "channels", channelId, "messages", (messages) => { + const newMessages = Object.fromEntries( + addMessages.map((item) => [item.id, item]), + ); + + const copy = { ...messages, ...newMessages }; + return copy; + }); +}; + +const deleteChannelMessage = (channelId: string, messageId: string) => { + setState( + "channel", + "channels", + channelId, + "messages", + messageId, + undefined, + ); +}; + +export { + setChannel, + deleteChannel, + setActiveChannel, + resetActiveChannel, + setText, + setChannelMessage, + setChannelMessages, + deleteChannelMessage, +}; diff --git a/src/store/channel/index.ts b/src/store/channel/index.ts index 06d90ae..d2c9cbe 100644 --- a/src/store/channel/index.ts +++ b/src/store/channel/index.ts @@ -1,3 +1,2 @@ export * from "./channel"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/channel/types.ts b/src/store/channel/types.ts index fc2ff58..813e7a9 100644 --- a/src/store/channel/types.ts +++ b/src/store/channel/types.ts @@ -2,7 +2,7 @@ import { IMessage } from "../message"; interface IChannelState { active?: string; - channels: Record; + channels: Record; } interface IChannel { @@ -12,7 +12,7 @@ interface IChannel { communityId?: string; creationDate?: number; text?: string; - messages?: Record; + messages?: Record; } export { type IChannelState, type IChannel }; diff --git a/src/store/community/actions.ts b/src/store/community/actions.ts deleted file mode 100644 index b7c3713..0000000 --- a/src/store/community/actions.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - ICreateCommunityRequest, - IUpdateCommunityRequest, - IFetchCommunityResponse, - ICreateCommunityResponse, - IUpdateCommunityResponse, - IRemoveCommunityResponse, - IFetchCommunityChannelsResponse, - IFetchCommunityRolesResponse, - IFetchCommunityMembersResponse, - IFetchCommunityInvitesResponse, -} from "../../api/community"; -import { IFetchUserCommunity } from "../../api/user"; - -enum CommunityActionTypes { - SET_COMMUNITY = "SET_COMMUNITY", - SET_ACTIVE_COMMUNITY = "SET_ACTIVE_COMMUNITY", - FETCH_COMMUNITY_START = "FETCH_COMMUNITY_START", - FETCH_COMMUNITY_FINISH = "FETCH_COMMUNITY_FINISH", - CREATE_COMMUNITY_START = "CREATE_COMMUNITY_START", - CREATE_COMMUNITY_FINISH = "CREATE_COMMUNITY_FINISH", - UPDATE_COMMUNITY_START = "UPDATE_COMMUNITY_START", - UPDATE_COMMUNITY_FINISH = "UPDATE_COMMUNITY_FINISH", - REMOVE_COMMUNITY_START = "REMOVE_COMMUNITY_START", - REMOVE_COMMUNITY_FINISH = "REMOVE_COMMUNITY_FINISH", - FETCH_COMMUNITY_CHANNELS_START = "FETCH_COMMUNITY_CHANNELS_START", - FETCH_COMMUNITY_CHANNELS_FINISH = "FETCH_COMMUNITY_CHANNELS_FINISH", - FETCH_COMMUNITY_ROLES_START = "FETCH_COMMUNITY_ROLES_START", - FETCH_COMMUNITY_ROLES_FINISH = "FETCH_COMMUNITY_ROLES_FINISH", - FETCH_COMMUNITY_MEMBERS_START = "FETCH_COMMUNITY_MEMBERS_START", - FETCH_COMMUNITY_MEMBERS_FINISH = "FETCH_COMMUNITY_MEMBERS_FINISH", - FETCH_COMMUNITY_INVITES_START = "FETCH_COMMUNITY_INVITES_START", - FETCH_COMMUNITY_INVITES_FINISH = "FETCH_COMMUNITY_INVITES_FINISH", -} - -type CommunityAction = - | { - type: CommunityActionTypes.SET_COMMUNITY; - payload: IFetchUserCommunity; - } - | { - type: CommunityActionTypes.SET_ACTIVE_COMMUNITY; - payload: string | undefined; - } - | { type: CommunityActionTypes.FETCH_COMMUNITY_START; payload: string } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_FINISH; - payload: IFetchCommunityResponse; - } - | { - type: CommunityActionTypes.CREATE_COMMUNITY_START; - payload: ICreateCommunityRequest; - } - | { - type: CommunityActionTypes.CREATE_COMMUNITY_FINISH; - payload: ICreateCommunityResponse; - } - | { - type: CommunityActionTypes.UPDATE_COMMUNITY_START; - payload: IUpdateCommunityRequest; - } - | { - type: CommunityActionTypes.UPDATE_COMMUNITY_FINISH; - payload: IUpdateCommunityResponse; - } - | { type: CommunityActionTypes.REMOVE_COMMUNITY_START; payload: string } - | { - type: CommunityActionTypes.REMOVE_COMMUNITY_FINISH; - payload: IRemoveCommunityResponse; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START; - payload: string; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_FINISH; - payload: IFetchCommunityChannelsResponse; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_ROLES_START; - payload: string; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_ROLES_FINISH; - payload: IFetchCommunityRolesResponse; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_START; - payload: string; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_FINISH; - payload: IFetchCommunityMembersResponse; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_INVITES_START; - payload: string; - } - | { - type: CommunityActionTypes.FETCH_COMMUNITY_INVITES_FINISH; - payload: IFetchCommunityInvitesResponse; - }; - -export { CommunityActionTypes, type CommunityAction }; diff --git a/src/store/community/community.ts b/src/store/community/community.ts index fbcde23..9668842 100644 --- a/src/store/community/community.ts +++ b/src/store/community/community.ts @@ -1,110 +1,81 @@ -import { - fetchCommunity, - createCommunity, - updateCommunity, - removeCommunity, - fetchCommunityChannels, - fetchCommunityRoles, - fetchCommunityMembers, - fetchCommunityInvites, -} from "../../services/community"; +import { deriveKey } from "../../services/crypto"; +import { IChannel } from "../channel"; +import { IInvite } from "../invite"; +import { IRole } from "../role"; import { setState } from "../state"; -import { CommunityActionTypes, CommunityAction } from "./actions"; -import { ICommunityState } from "./types"; +import { IUser } from "../user"; +import { ICommunity } from "./types"; -function communityReducer(_state: ICommunityState, action: CommunityAction) { - switch (action.type) { - case CommunityActionTypes.SET_COMMUNITY: - setState( - "community", - "communities", - action.payload.id, - action.payload, - ); - break; - case CommunityActionTypes.SET_ACTIVE_COMMUNITY: - setState("community", "active", action.payload); - break; - case CommunityActionTypes.FETCH_COMMUNITY_START: - fetchCommunity(action.payload); - break; - case CommunityActionTypes.FETCH_COMMUNITY_FINISH: - setState( - "community", - "communities", - action.payload.id, - action.payload, - ); - break; - case CommunityActionTypes.CREATE_COMMUNITY_START: - createCommunity(action.payload.name); - break; - case CommunityActionTypes.CREATE_COMMUNITY_FINISH: - setState( - "community", - "communities", - action.payload.id, - action.payload, - ); - break; - case CommunityActionTypes.UPDATE_COMMUNITY_START: - updateCommunity( - action.payload.id, - action.payload.name, - action.payload.description, - ); - break; - case CommunityActionTypes.UPDATE_COMMUNITY_FINISH: - setState( - "community", - "communities", - action.payload.id, - action.payload, - ); - break; - case CommunityActionTypes.REMOVE_COMMUNITY_START: - removeCommunity(action.payload); - break; - case CommunityActionTypes.REMOVE_COMMUNITY_FINISH: - setState("community", "communities", (communities) => { - const copy = { ...communities }; - delete copy[action.payload.id]; - return copy; - }); - break; - case CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START: - fetchCommunityChannels(action.payload); - break; - case CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_FINISH: - setState("community", "communities", action.payload.id, { - channels: action.payload.channels.map((channel) => channel.id), - }); - break; - case CommunityActionTypes.FETCH_COMMUNITY_ROLES_START: - fetchCommunityRoles(action.payload); - break; - case CommunityActionTypes.FETCH_COMMUNITY_ROLES_FINISH: - setState("community", "communities", action.payload.id, { - roles: action.payload.roles.map((role) => role.id), - }); - break; - case CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_START: - fetchCommunityMembers(action.payload); - break; - case CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_FINISH: - setState("community", "communities", action.payload.id, { - members: action.payload.members.map((member) => member.id), - }); - break; - case CommunityActionTypes.FETCH_COMMUNITY_INVITES_START: - fetchCommunityInvites(action.payload); - break; - case CommunityActionTypes.FETCH_COMMUNITY_INVITES_FINISH: - setState("community", "communities", action.payload.id, { - invites: action.payload.invites.map((invite) => invite.id), - }); - break; - } -} +const setCommunity = (community: ICommunity) => { + setState("community", "communities", community.id, community); +}; -export { communityReducer }; +const deleteCommunity = (communityId: string) => { + setState("community", "communities", communityId, undefined); +}; + +const setActiveCommunity = (communityId: string) => { + setState("community", "active", communityId); +}; + +const resetActiveCommunity = () => { + setState("community", "active", undefined); +}; + +const setCommunityChannels = (communityId: string, channels: IChannel[]) => { + setState("community", "communities", communityId, { + channels: channels.map((channel) => channel.id), + }); +}; + +const setCommunityRoles = (communityId: string, roles: IRole[]) => { + setState("community", "communities", communityId, { + roles: roles.map((role) => role.id), + }); +}; + +const setCommunityMembers = (communityId: string, members: IUser[]) => { + setState("community", "communities", communityId, { + members: members.map((member) => member.id), + }); +}; + +const setCommunityInvites = (communityId: string, invites: IInvite[]) => { + setState("community", "communities", communityId, { + invites: invites.map((invite) => invite.id), + }); +}; + +const setCommunityEncryptionKey = ( + communityId: string, + encryptionKey: string, +) => { + setState( + "community", + "communities", + communityId, + "encryptionKey", + encryptionKey, + ); + + deriveKey(encryptionKey).then((derivedKey) => + setCommunityDerivedKey(communityId, derivedKey), + ); +}; + +const setCommunityDerivedKey = (communityId: string, derivedKey: CryptoKey) => { + setState("community", "communities", communityId, "derivedKey", derivedKey); +}; + +export { + setCommunity, + deleteCommunity, + setActiveCommunity, + resetActiveCommunity, + setCommunityChannels, + setCommunityRoles, + setCommunityMembers, + setCommunityInvites, + setCommunityEncryptionKey, + setCommunityDerivedKey, +}; diff --git a/src/store/community/index.ts b/src/store/community/index.ts index 8162b36..1a806be 100644 --- a/src/store/community/index.ts +++ b/src/store/community/index.ts @@ -1,3 +1,2 @@ export * from "./community"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/community/types.ts b/src/store/community/types.ts index cab5dce..bf033ae 100644 --- a/src/store/community/types.ts +++ b/src/store/community/types.ts @@ -1,6 +1,6 @@ interface ICommunityState { active?: string; - communities: Record; + communities: Record; } interface ICommunity { @@ -13,6 +13,8 @@ interface ICommunity { roles?: string[]; members?: string[]; invites?: string[]; + encryptionKey?: string; + derivedKey?: CryptoKey; } export { type ICommunityState, type ICommunity }; diff --git a/src/store/invite/actions.ts b/src/store/invite/actions.ts deleted file mode 100644 index 884e6e4..0000000 --- a/src/store/invite/actions.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { IFetchCommunityInvite } from "../../api/community"; -import { - IFetchInviteResponse, - IRemoveInviteResponse, - IAcceptInviteResponse, -} from "../../api/invite"; - -enum InviteActionTypes { - SET_INVITE = "SET_INVITE", - FETCH_INVITE_START = "FETCH_INVITE_START", - FETCH_INVITE_FINISH = "FETCH_INVITE_FINISH", - REMOVE_INVITE_START = "REMOVE_INVITE_START", - REMOVE_INVITE_FINISH = "REMOVE_INVITE_FINISH", - ACCEPT_INVITE_START = "ACCEPT_INVITE_START", - ACCEPT_INVITE_FINISH = "ACCEPT_INVITE_FINISH", -} - -type InviteAction = - | { - type: InviteActionTypes.SET_INVITE; - payload: IFetchCommunityInvite; - } - | { type: InviteActionTypes.FETCH_INVITE_START; payload: string } - | { - type: InviteActionTypes.FETCH_INVITE_FINISH; - payload: IFetchInviteResponse; - } - | { type: InviteActionTypes.REMOVE_INVITE_START; payload: string } - | { - type: InviteActionTypes.REMOVE_INVITE_FINISH; - payload: IRemoveInviteResponse; - } - | { type: InviteActionTypes.ACCEPT_INVITE_START; payload: string } - | { - type: InviteActionTypes.ACCEPT_INVITE_FINISH; - payload: IAcceptInviteResponse; - }; - -export { InviteActionTypes, type InviteAction }; diff --git a/src/store/invite/index.ts b/src/store/invite/index.ts index 78cfeec..d0a9cdf 100644 --- a/src/store/invite/index.ts +++ b/src/store/invite/index.ts @@ -1,3 +1,2 @@ export * from "./invite"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/invite/invite.ts b/src/store/invite/invite.ts index 5fdf5be..70ad226 100644 --- a/src/store/invite/invite.ts +++ b/src/store/invite/invite.ts @@ -1,35 +1,12 @@ -import { fetchInvite, removeInvite, acceptInvite } from "../../services/invite"; import { setState } from "../state"; -import { InviteActionTypes, InviteAction } from "./actions"; -import { IInviteState } from "./types"; +import { IInvite } from "./types"; -function inviteReducer(_state: IInviteState, action: InviteAction) { - switch (action.type) { - case InviteActionTypes.SET_INVITE: - setState("invite", "invites", action.payload.id, action.payload); - break; - case InviteActionTypes.FETCH_INVITE_START: - fetchInvite(action.payload); - break; - case InviteActionTypes.FETCH_INVITE_FINISH: - setState("invite", "invites", action.payload.id, action.payload); - break; - case InviteActionTypes.REMOVE_INVITE_START: - removeInvite(action.payload); - break; - case InviteActionTypes.REMOVE_INVITE_FINISH: - setState("invite", "invites", (invites) => { - const copy = { ...invites }; - delete copy[action.payload.id]; - return copy; - }); - break; - case InviteActionTypes.ACCEPT_INVITE_START: - acceptInvite(action.payload); - break; - case InviteActionTypes.ACCEPT_INVITE_FINISH: - break; - } -} +const setInvite = (invite: IInvite) => { + setState("invite", "invites", invite.id, invite); +}; -export { inviteReducer }; +const deleteInvite = (inviteId: string) => { + setState("invite", "invites", inviteId, undefined); +}; + +export { setInvite, deleteInvite }; diff --git a/src/store/invite/types.ts b/src/store/invite/types.ts index f606cc6..1446937 100644 --- a/src/store/invite/types.ts +++ b/src/store/invite/types.ts @@ -1,5 +1,5 @@ interface IInviteState { - invites: Record; + invites: Record; } interface IInvite { diff --git a/src/store/message/actions.ts b/src/store/message/actions.ts deleted file mode 100644 index 23e152e..0000000 --- a/src/store/message/actions.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - ICreateMessageRequest, - IUpdateMessageRequest, - IFetchMessageResponse, - ICreateMessageResponse, - IUpdateMessageResponse, - IRemoveMessageResponse, -} from "../../api/message"; - -enum MessageActionTypes { - FETCH_MESSAGE_START = "FETCH_MESSAGE_START", - FETCH_MESSAGE_FINISH = "FETCH_MESSAGE_FINISH", - CREATE_MESSAGE_START = "CREATE_MESSAGE_START", - CREATE_MESSAGE_FINISH = "CREATE_MESSAGE_FINISH", - UPDATE_MESSAGE_START = "UPDATE_MESSAGE_START", - UPDATE_MESSAGE_FINISH = "UPDATE_MESSAGE_FINISH", - REMOVE_MESSAGE_START = "REMOVE_MESSAGE_START", - REMOVE_MESSAGE_FINISH = "REMOVE_MESSAGE_FINISH", -} - -type MessageAction = - | { type: MessageActionTypes.FETCH_MESSAGE_START; payload: string } - | { - type: MessageActionTypes.FETCH_MESSAGE_FINISH; - payload: IFetchMessageResponse; - } - | { - type: MessageActionTypes.CREATE_MESSAGE_START; - payload: ICreateMessageRequest; - } - | { - type: MessageActionTypes.CREATE_MESSAGE_FINISH; - payload: ICreateMessageResponse; - } - | { - type: MessageActionTypes.UPDATE_MESSAGE_START; - payload: IUpdateMessageRequest; - } - | { - type: MessageActionTypes.UPDATE_MESSAGE_FINISH; - payload: IUpdateMessageResponse; - } - | { type: MessageActionTypes.REMOVE_MESSAGE_START; payload: string } - | { - type: MessageActionTypes.REMOVE_MESSAGE_FINISH; - payload: IRemoveMessageResponse; - }; - -export { MessageActionTypes, type MessageAction }; diff --git a/src/store/message/index.ts b/src/store/message/index.ts index a6332db..d001067 100644 --- a/src/store/message/index.ts +++ b/src/store/message/index.ts @@ -1,3 +1,2 @@ export * from "./message"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/message/message.ts b/src/store/message/message.ts index 5166923..ef80650 100644 --- a/src/store/message/message.ts +++ b/src/store/message/message.ts @@ -1,59 +1,22 @@ -import { - fetchMessage, - createMessage, - updateMessage, - removeMessage, -} from "../../services/message"; import { setState } from "../state"; -import { MessageActionTypes, MessageAction } from "./actions"; -import { IMessageState } from "./types"; +import { IMessage } from "./types"; -function messageReducer(_state: IMessageState, action: MessageAction) { - switch (action.type) { - case MessageActionTypes.FETCH_MESSAGE_START: - fetchMessage(action.payload); - break; - case MessageActionTypes.FETCH_MESSAGE_FINISH: - setState("message", "message", action.payload); - setState( - "channel", - "channels", - action.payload.channelId, - action.payload, - ); - break; - case MessageActionTypes.CREATE_MESSAGE_START: - createMessage(action.payload.text, action.payload.channelId); - break; - case MessageActionTypes.CREATE_MESSAGE_FINISH: - setState("message", "message", action.payload); - setState( - "channel", - "channels", - action.payload.channelId, - "messages", - action.payload.id, - action.payload, - ); - break; - case MessageActionTypes.UPDATE_MESSAGE_START: - updateMessage(action.payload.id, action.payload.text); - break; - case MessageActionTypes.UPDATE_MESSAGE_FINISH: - setState("message", "message", action.payload); - setState( - "channel", - "channels", - action.payload.channelId, - action.payload, - ); - break; - case MessageActionTypes.REMOVE_MESSAGE_START: - removeMessage(action.payload); - break; - case MessageActionTypes.REMOVE_MESSAGE_FINISH: - break; +const setMessage = (message: IMessage) => { + setState("message", "message", message); + if (message.channelId) { + setState( + "channel", + "channels", + message.channelId, + "messages", + message.id, + message, + ); } -} +}; -export { messageReducer }; +const deleteMessage = () => { + setState("message", "message", undefined); +}; + +export { setMessage, deleteMessage }; diff --git a/src/store/message/types.ts b/src/store/message/types.ts index 825a759..fe2e022 100644 --- a/src/store/message/types.ts +++ b/src/store/message/types.ts @@ -5,11 +5,13 @@ interface IMessageState { interface IMessage { id: string; text: string; + iv: string; editHistory?: string[]; edited: boolean; ownerId: string; channelId?: string; creationDate: number; + decryptionStatus?: boolean; } export { type IMessageState, type IMessage }; diff --git a/src/store/reducers.ts b/src/store/reducers.ts deleted file mode 100644 index 3c3a8c8..0000000 --- a/src/store/reducers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { IState } from "./types"; -import { Action } from "./actions"; -import { AppAction, appReducer } from "./app"; -import { AuthAction, authReducer } from "./auth"; -import { UserAction, userReducer } from "./user"; -import { CommunityAction, communityReducer } from "./community"; -import { ChannelAction, channelReducer } from "./channel"; -import { RoleAction, roleReducer } from "./role"; -import { SessionAction, sessionReducer } from "./session"; -import { InviteAction, inviteReducer } from "./invite"; -import { MessageAction, messageReducer } from "./message"; - -function reducer(state: IState, action: Action) { - appReducer(state.app, action as AppAction); - authReducer(state.auth, action as AuthAction); - userReducer(state.user, action as UserAction); - communityReducer(state.community, action as CommunityAction); - channelReducer(state.channel, action as ChannelAction); - roleReducer(state.role, action as RoleAction); - sessionReducer(state.session, action as SessionAction); - inviteReducer(state.invite, action as InviteAction); - messageReducer(state.message, action as MessageAction); -} - -export { reducer }; diff --git a/src/store/role/actions.ts b/src/store/role/actions.ts deleted file mode 100644 index f33d464..0000000 --- a/src/store/role/actions.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { IFetchCommunityRole } from "../../api/community"; -import { - ICreateRoleRequest, - IUpdateRoleRequest, - IFetchRoleResponse, - ICreateRoleResponse, - IUpdateRoleResponse, - IRemoveRoleResponse, -} from "../../api/role"; - -enum RoleActionTypes { - SET_ROLE = "SET_ROLE", - FETCH_ROLE_START = "FETCH_ROLE_START", - FETCH_ROLE_FINISH = "FETCH_ROLE_FINISH", - CREATE_ROLE_START = "CREATE_ROLE_START", - CREATE_ROLE_FINISH = "CREATE_ROLE_FINISH", - UPDATE_ROLE_START = "UPDATE_ROLE_START", - UPDATE_ROLE_FINISH = "UPDATE_ROLE_FINISH", - REMOVE_ROLE_START = "REMOVE_ROLE_START", - REMOVE_ROLE_FINISH = "REMOVE_ROLE_FINISH", -} - -type RoleAction = - | { - type: RoleActionTypes.SET_ROLE; - payload: IFetchCommunityRole; - } - | { type: RoleActionTypes.FETCH_ROLE_START; payload: string } - | { - type: RoleActionTypes.FETCH_ROLE_FINISH; - payload: IFetchRoleResponse; - } - | { type: RoleActionTypes.CREATE_ROLE_START; payload: ICreateRoleRequest } - | { - type: RoleActionTypes.CREATE_ROLE_FINISH; - payload: ICreateRoleResponse; - } - | { type: RoleActionTypes.UPDATE_ROLE_START; payload: IUpdateRoleRequest } - | { - type: RoleActionTypes.UPDATE_ROLE_FINISH; - payload: IUpdateRoleResponse; - } - | { type: RoleActionTypes.REMOVE_ROLE_START; payload: string } - | { - type: RoleActionTypes.REMOVE_ROLE_FINISH; - payload: IRemoveRoleResponse; - }; - -export { RoleActionTypes, type RoleAction }; diff --git a/src/store/role/index.ts b/src/store/role/index.ts index f1b9dfc..2ae6a59 100644 --- a/src/store/role/index.ts +++ b/src/store/role/index.ts @@ -1,3 +1,2 @@ export * from "./role"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/role/role.ts b/src/store/role/role.ts index 383eb2c..24c557d 100644 --- a/src/store/role/role.ts +++ b/src/store/role/role.ts @@ -1,47 +1,12 @@ -import { - fetchRole, - createRole, - updateRole, - removeRole, -} from "../../services/role"; import { setState } from "../state"; -import { RoleActionTypes, RoleAction } from "./actions"; -import { IRoleState } from "./types"; +import { IRole } from "./types"; -function roleReducer(_state: IRoleState, action: RoleAction) { - switch (action.type) { - case RoleActionTypes.SET_ROLE: - setState("role", "roles", action.payload.id, action.payload); - break; - case RoleActionTypes.FETCH_ROLE_START: - fetchRole(action.payload); - break; - case RoleActionTypes.FETCH_ROLE_FINISH: - setState("role", "roles", action.payload.id, action.payload); - break; - case RoleActionTypes.CREATE_ROLE_START: - createRole(action.payload.name, action.payload.communityId); - break; - case RoleActionTypes.CREATE_ROLE_FINISH: - setState("role", "roles", action.payload.id, action.payload); - break; - case RoleActionTypes.UPDATE_ROLE_START: - updateRole(action.payload.id, action.payload.name); - break; - case RoleActionTypes.UPDATE_ROLE_FINISH: - setState("role", "roles", action.payload.id, action.payload); - break; - case RoleActionTypes.REMOVE_ROLE_START: - removeRole(action.payload); - break; - case RoleActionTypes.REMOVE_ROLE_FINISH: - setState("role", "roles", (roles) => { - const copy = { ...roles }; - delete copy[action.payload.id]; - return copy; - }); - break; - } -} +const setRole = (role: IRole) => { + setState("role", "roles", role.id, role); +}; -export { roleReducer }; +const deleteRole = (roleId: string) => { + setState("role", "roles", roleId, undefined); +}; + +export { setRole, deleteRole }; diff --git a/src/store/role/types.ts b/src/store/role/types.ts index 539caf9..b876f8b 100644 --- a/src/store/role/types.ts +++ b/src/store/role/types.ts @@ -1,5 +1,5 @@ interface IRoleState { - roles: Record; + roles: Record; } interface IRole { diff --git a/src/store/session/actions.ts b/src/store/session/actions.ts deleted file mode 100644 index 9cd2fd9..0000000 --- a/src/store/session/actions.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { IFetchUserSession } from "../../api/user"; -import { - IFetchSessionResponse, - IRemoveSessionResponse, -} from "../../api/session"; - -enum SessionActionTypes { - SET_SESSION = "SET_SESSION", - FETCH_SESSION_START = "FETCH_SESSION_START", - FETCH_SESSION_FINISH = "FETCH_SESSION_FINISH", - REMOVE_SESSION_START = "REMOVE_SESSION_START", - REMOVE_SESSION_FINISH = "REMOVE_SESSION_FINISH", -} - -type SessionAction = - | { - type: SessionActionTypes.SET_SESSION; - payload: IFetchUserSession; - } - | { type: SessionActionTypes.FETCH_SESSION_START; payload: string } - | { - type: SessionActionTypes.FETCH_SESSION_FINISH; - payload: IFetchSessionResponse; - } - | { type: SessionActionTypes.REMOVE_SESSION_START; payload: string } - | { - type: SessionActionTypes.REMOVE_SESSION_FINISH; - payload: IRemoveSessionResponse; - }; - -export { SessionActionTypes, type SessionAction }; diff --git a/src/store/session/index.ts b/src/store/session/index.ts index 1024550..69bf84b 100644 --- a/src/store/session/index.ts +++ b/src/store/session/index.ts @@ -1,3 +1,2 @@ export * from "./session"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/session/session.ts b/src/store/session/session.ts index f99d218..e425abc 100644 --- a/src/store/session/session.ts +++ b/src/store/session/session.ts @@ -1,30 +1,12 @@ -import { fetchSession, removeSession } from "../../services/session"; import { setState } from "../state"; -import { SessionActionTypes, SessionAction } from "./actions"; -import { ISessionState } from "./types"; +import { ISession } from "./types"; -function sessionReducer(_state: ISessionState, action: SessionAction) { - switch (action.type) { - case SessionActionTypes.SET_SESSION: - setState("session", "sessions", action.payload.id, action.payload); - break; - case SessionActionTypes.FETCH_SESSION_START: - fetchSession(action.payload); - break; - case SessionActionTypes.FETCH_SESSION_FINISH: - setState("session", "sessions", action.payload.id, action.payload); - break; - case SessionActionTypes.REMOVE_SESSION_START: - removeSession(action.payload); - break; - case SessionActionTypes.REMOVE_SESSION_FINISH: - setState("session", "sessions", (sessions) => { - const copy = { ...sessions }; - delete copy[action.payload.id]; - return copy; - }); - break; - } -} +const setSession = (session: ISession) => { + setState("session", "sessions", session.id, session); +}; -export { sessionReducer }; +const deleteSession = (sessionId: string) => { + setState("session", "sessions", sessionId, undefined); +}; + +export { setSession, deleteSession }; diff --git a/src/store/session/types.ts b/src/store/session/types.ts index bee14e1..c62e3f8 100644 --- a/src/store/session/types.ts +++ b/src/store/session/types.ts @@ -1,11 +1,14 @@ interface ISessionState { - sessions: Record; + sessions: Record; } interface ISession { id: string; userId?: string; + name?: string; + userAgent?: string; creationDate?: number; + refreshDate?: number; } export { type ISessionState, type ISession }; diff --git a/src/store/state.ts b/src/store/state.ts index 536fa65..991b1d3 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -1,7 +1,5 @@ import { createStore } from "solid-js/store"; import { IState } from "./types"; -import { Action } from "./actions"; -import { reducer } from "./reducers"; const [state, setState] = createStore({ app: { @@ -13,6 +11,7 @@ const [state, setState] = createStore({ }, }, auth: { + registerSuccess: undefined, loggedIn: undefined, session: undefined, }, @@ -40,8 +39,4 @@ const [state, setState] = createStore({ }, }); -function dispatch(action: Action) { - reducer(state, action); -} - -export { state, setState, dispatch }; +export { state, setState }; diff --git a/src/store/user/actions.ts b/src/store/user/actions.ts deleted file mode 100644 index e292919..0000000 --- a/src/store/user/actions.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { IFetchCommunityMember } from "../../api/community"; -import { - IFetchLoggedUserResponse, - IFetchUserResponse, - IFetchUserSessionsResponse, - IFetchUserCommunitiesResponse, -} from "../../api/user"; - -enum UserActionTypes { - SET_USER = "SET_USER", - FETCH_LOGGED_USER_ID_START = "FETCH_LOGGED_USER_ID_START", - FETCH_LOGGED_USER_ID_FINISH = "FETCH_LOGGED_USER_ID_FINISH", - FETCH_USER_START = "FETCH_USER_START", - FETCH_USER_FINISH = "FETCH_USER_FINISH", - FETCH_USER_SESSIONS_START = "FETCH_USER_SESSIONS_START", - FETCH_USER_SESSIONS_FINISH = "FETCH_USER_SESSIONS_FINISH", - FETCH_USER_COMMUNITIES_START = "FETCH_USER_COMMUNITIES_START", - FETCH_USER_COMMUNITIES_FINISH = "FETCH_USER_COMMUNITIES_FINISH", -} - -type UserAction = - | { - type: UserActionTypes.SET_USER; - payload: IFetchCommunityMember; - } - | { type: UserActionTypes.FETCH_LOGGED_USER_ID_START } - | { - type: UserActionTypes.FETCH_LOGGED_USER_ID_FINISH; - payload: IFetchLoggedUserResponse; - } - | { type: UserActionTypes.FETCH_USER_START; payload: string } - | { type: UserActionTypes.FETCH_USER_FINISH; payload: IFetchUserResponse } - | { type: UserActionTypes.FETCH_USER_SESSIONS_START; payload: string } - | { - type: UserActionTypes.FETCH_USER_SESSIONS_FINISH; - payload: IFetchUserSessionsResponse; - } - | { type: UserActionTypes.FETCH_USER_COMMUNITIES_START; payload: string } - | { - type: UserActionTypes.FETCH_USER_COMMUNITIES_FINISH; - payload: IFetchUserCommunitiesResponse; - }; - -export { UserActionTypes, type UserAction }; diff --git a/src/store/user/index.ts b/src/store/user/index.ts index 1611bda..f040ea0 100644 --- a/src/store/user/index.ts +++ b/src/store/user/index.ts @@ -1,3 +1,2 @@ export * from "./user"; -export * from "./actions"; export * from "./types"; diff --git a/src/store/user/user.ts b/src/store/user/user.ts index 884e576..2a8792a 100644 --- a/src/store/user/user.ts +++ b/src/store/user/user.ts @@ -1,49 +1,31 @@ import { - fetchLoggedUser, - fetchUser, - fetchUserSessions, - fetchUserCommunities, -} from "../../services/user"; + IFetchUserCommunitiesResponse, + IFetchUserSessionsResponse, +} from "../../api/user"; +import { loadCommunityCryptoStates } from "../../services/community"; import { setState } from "../state"; -import { UserActionTypes, UserAction } from "./actions"; -import { IUserState } from "./types"; +import { IUser } from "./types"; -function userReducer(_state: IUserState, action: UserAction) { - switch (action.type) { - case UserActionTypes.SET_USER: - setState("user", "users", action.payload.id, action.payload); - break; - case UserActionTypes.FETCH_LOGGED_USER_ID_START: - fetchLoggedUser(); - break; - case UserActionTypes.FETCH_LOGGED_USER_ID_FINISH: - setState("user", "loggedUserId", action.payload.id); - break; - case UserActionTypes.FETCH_USER_START: - fetchUser(action.payload); - break; - case UserActionTypes.FETCH_USER_FINISH: - setState("user", "users", action.payload.id, action.payload); - break; - case UserActionTypes.FETCH_USER_SESSIONS_START: - fetchUserSessions(action.payload); - break; - case UserActionTypes.FETCH_USER_SESSIONS_FINISH: - setState("user", "users", action.payload.id, { - sessions: action.payload.sessions.map((session) => session.id), - }); - break; - case UserActionTypes.FETCH_USER_COMMUNITIES_START: - fetchUserCommunities(action.payload); - break; - case UserActionTypes.FETCH_USER_COMMUNITIES_FINISH: - setState("user", "users", action.payload.id, { - communities: action.payload.communities.map( - (community) => community.id, - ), - }); - break; - } -} +const setUser = (user: IUser) => { + setState("user", "users", user.id, user); +}; -export { userReducer }; +const setLoggedUserId = (userId: string) => { + setState("user", "loggedUserId", userId); +}; + +const setUserSessions = (sessions: IFetchUserSessionsResponse) => { + setState("user", "users", sessions.id, { + sessions: sessions.sessions.map((session) => session.id), + }); +}; + +const setUserCommunities = (communities: IFetchUserCommunitiesResponse) => { + setState("user", "users", communities.id, { + communities: communities.communities.map((community) => community.id), + }); + + loadCommunityCryptoStates(); +}; + +export { setUser, setLoggedUserId, setUserSessions, setUserCommunities }; diff --git a/src/views/AddCommunityModalView/AddCommunityModalView.tsx b/src/views/AddCommunityModalView/AddCommunityModalView.tsx new file mode 100644 index 0000000..655f06f --- /dev/null +++ b/src/views/AddCommunityModalView/AddCommunityModalView.tsx @@ -0,0 +1,90 @@ +import { createSignal, type Component } from "solid-js"; +import { IAddCommunityModalViewProps } from "./types"; +import { createCommunity } from "../../services/community"; +import { acceptInvite } from "../../services/invite"; +import { Input } from "../../components/Input"; + +const AddCommunityModalView: Component = ( + props: IAddCommunityModalViewProps, +) => { + const [getCommunityName, setCommunityName] = createSignal(""); + const [getInviteId, setInviteId] = createSignal(""); + + const onCreateCommunity = () => { + const communityName = getCommunityName(); + if (!communityName || communityName.trim().length < 1) { + return; + } + + createCommunity(communityName); + + setCommunityName(""); + + props.onClose?.(); + }; + + const onJoinCommunity = () => { + const inviteId = getInviteId(); + if (!inviteId || inviteId.trim().length < 1) { + return; + } + + acceptInvite(inviteId); + + setInviteId(""); + + props.onClose?.(); + }; + + const createCommunityHtml = () => ( + <> +

+ Create a new Community +

+ + + ); + + const joinCommunityHtml = () => ( + <> +

+ Join an existing Community +

+ + + ); + + return ( +
+ + + + +
+ ); +}; + +export default AddCommunityModalView; diff --git a/src/views/AddCommunityModalView/index.ts b/src/views/AddCommunityModalView/index.ts new file mode 100644 index 0000000..ccfe80d --- /dev/null +++ b/src/views/AddCommunityModalView/index.ts @@ -0,0 +1,2 @@ +export * from "./AddCommunityModalView"; +export * from "./types"; diff --git a/src/views/AddCommunityModalView/types.ts b/src/views/AddCommunityModalView/types.ts new file mode 100644 index 0000000..921e3d9 --- /dev/null +++ b/src/views/AddCommunityModalView/types.ts @@ -0,0 +1,6 @@ +interface IAddCommunityModalViewProps { + dialogRef?: (element: HTMLDialogElement) => void; + onClose?: () => void; +} + +export { type IAddCommunityModalViewProps }; diff --git a/src/views/AppView/AppView.tsx b/src/views/AppView/AppView.tsx index 27a44b7..6072e65 100644 --- a/src/views/AppView/AppView.tsx +++ b/src/views/AppView/AppView.tsx @@ -2,36 +2,31 @@ import { createEffect, onMount, type Component } from "solid-js"; import { ChannelView } from "../ChannelView"; import { ChatView } from "../ChatView"; import { MemberView } from "../MemberView"; -import { dispatch, state } from "../../store/state"; -import { UserActionTypes } from "../../store/user"; -import { AuthActionTypes } from "../../store/auth"; +import { state } from "../../store/state"; import { HomeView } from "../HomeView"; import { CommunityView } from "../CommunityView"; import { ModalView } from "../ModalView"; import { useNavigate } from "@solidjs/router"; import { connectWs } from "../../services/websocket"; +import { fetchRefresh } from "../../services/auth"; +import { fetchLoggedUser, fetchUserCommunities } from "../../services/user"; const AppView: Component = () => { const navigate = useNavigate(); onMount(() => { - dispatch({ - type: AuthActionTypes.FETCH_REFRESH_START, - }); + fetchRefresh(); }); createEffect(() => { if (state.auth.session?.token) { - dispatch({ type: UserActionTypes.FETCH_LOGGED_USER_ID_START }); + fetchLoggedUser(); } }); createEffect(() => { if (state.user.loggedUserId) { - dispatch({ - type: UserActionTypes.FETCH_USER_COMMUNITIES_START, - payload: state.user.loggedUserId, - }); + fetchUserCommunities(state.user.loggedUserId); } }); diff --git a/src/views/ChannelView/ChannelView.tsx b/src/views/ChannelView/ChannelView.tsx index 694c034..1403fb5 100644 --- a/src/views/ChannelView/ChannelView.tsx +++ b/src/views/ChannelView/ChannelView.tsx @@ -1,10 +1,12 @@ import { createEffect, createMemo, type Component } from "solid-js"; -import { dispatch, state } from "../../store/state"; -import { CommunityActionTypes, ICommunity } from "../../store/community"; -import { ChannelActionTypes } from "../../store/channel"; +import { state } from "../../store/state"; +import { ICommunity } from "../../store/community"; import { Channel } from "../../components/Channel"; import { CommunityBar } from "../../components/CommunityBar"; -import { AppActionTypes } from "../../store/app"; +import { setCommunitySettingsOpen } from "../../store/app"; +import { fetchCommunityChannels } from "../../services/community"; +import { fetchChannel } from "../../services/channel"; +import { setActiveChannel } from "../../store/channel"; const ChannelView: Component = () => { const channelIds = createMemo(() => { @@ -28,29 +30,17 @@ const ChannelView: Component = () => { createEffect(() => { if (state.community.active) { - dispatch({ - type: CommunityActionTypes.FETCH_COMMUNITY_CHANNELS_START, - payload: state.community.active, - }); + fetchCommunityChannels(state.community.active); } }); const onCommunitySettingsClick = () => { - dispatch({ - type: AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN, - payload: true, - }); + setCommunitySettingsOpen(true); }; const onChannelClick = (id: string) => { - dispatch({ - type: ChannelActionTypes.FETCH_CHANNEL_START, - payload: id, - }); - dispatch({ - type: ChannelActionTypes.SET_ACTIVE_CHANNEL, - payload: id, - }); + fetchChannel(id); + setActiveChannel(id); }; const mapChannel = (channelId: string) => { diff --git a/src/views/ChatView/ChatView.tsx b/src/views/ChatView/ChatView.tsx index 4a0774a..73b0e42 100644 --- a/src/views/ChatView/ChatView.tsx +++ b/src/views/ChatView/ChatView.tsx @@ -1,11 +1,13 @@ -import { createEffect, createMemo, onMount, type Component } from "solid-js"; +import { createMemo, onMount, type Component } from "solid-js"; import { ChannelBar } from "../../components/ChannelBar"; import { MessageBar } from "../../components/MessageBar"; import { Message } from "../../components/Message"; -import { dispatch, state } from "../../store/state"; -import { UserActionTypes } from "../../store/user"; -import { ChannelActionTypes, IChannel } from "../../store/channel"; -import { MessageActionTypes } from "../../store/message"; +import { state } from "../../store/state"; +import { IChannel, setText } from "../../store/channel"; +import { fetchChannelMessages } from "../../services/channel"; +import { fetchUser } from "../../services/user"; +import { createMessage } from "../../services/message"; +import { IMessage } from "../../store/message"; const ChatView: Component = () => { let scrollRef: HTMLUListElement | undefined; @@ -46,14 +48,13 @@ const ChatView: Component = () => { autoScroll = true; - dispatch({ - type: ChannelActionTypes.FETCH_CHANNEL_MESSAGES_START, - payload: activeChannelId, - }); + if (state.community.active) { + fetchChannelMessages(activeChannelId, state.community.active); + } return state.channel.channels[activeChannelId]; }); - const messages = createMemo(() => { + const messages = createMemo(() => { const channelId = channelInfo()?.id; if (!channelId) { return []; @@ -69,7 +70,14 @@ const ChatView: Component = () => { setTimeout(scrollToBottom, 20); setTimeout(scrollToBottom, 100); - return channel.messages ?? []; + const encryptedMessages = channel.messages; + if (!encryptedMessages) { + return []; + } + + return Object.values(encryptedMessages) + .filter((msg): msg is IMessage => msg !== undefined) + .sort((a, b) => a.creationDate - b.creationDate); }); onMount(() => { @@ -79,10 +87,7 @@ const ChatView: Component = () => { }); const onProfileClick = (userId: string) => { - dispatch({ - type: UserActionTypes.FETCH_USER_START, - payload: userId, - }); + fetchUser(userId); }; const onChangeMessageText = (text: string) => { @@ -91,20 +96,14 @@ const ChatView: Component = () => { return; } - dispatch({ - type: ChannelActionTypes.SET_TEXT, - payload: { - id: channel.id, - text: text, - }, - }); + setText(channel.id, text); }; const onMessageSend = () => { autoScroll = true; const channel = channelInfo(); - if (!channel?.id) { + if (!channel?.id || !state.community.active) { return; } @@ -113,20 +112,8 @@ const ChatView: Component = () => { return; } - dispatch({ - type: ChannelActionTypes.SET_TEXT, - payload: { - id: channel.id, - text: "", - }, - }); - dispatch({ - type: MessageActionTypes.CREATE_MESSAGE_START, - payload: { - channelId: channel.id, - text: text, - }, - }); + setText(channel.id, ""); + createMessage(state.community.active, channel.id, text); }; return ( @@ -142,26 +129,30 @@ const ChatView: Component = () => { onScrollEnd={handleScroll} class="h-full list flex flex-col p-2 pt-18 pb-24 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500 scrollbar-track-gray-800" > - {Object.entries(messages()).map(([_key, message]) => ( + {messages().map((message) => ( ))} - + {channelInfo() ? ( + + ) : undefined}
); diff --git a/src/components/CommunitySettingsModal/CommunitySettingsModal.tsx b/src/views/CommunitySettingsModalView/CommunitySettingsModalView.tsx similarity index 61% rename from src/components/CommunitySettingsModal/CommunitySettingsModal.tsx rename to src/views/CommunitySettingsModalView/CommunitySettingsModalView.tsx index 781b5a9..5932049 100644 --- a/src/components/CommunitySettingsModal/CommunitySettingsModal.tsx +++ b/src/views/CommunitySettingsModalView/CommunitySettingsModalView.tsx @@ -1,12 +1,15 @@ import type { Component } from "solid-js"; -import { ICommunitySettingsModalProps } from "./types"; +import { ICommunitySettingsModalViewProps } from "./types"; -const CommunitySettingsModal: Component = ( - props, -) => { +const CommunitySettingsModalView: Component< + ICommunitySettingsModalViewProps +> = (props: ICommunitySettingsModalViewProps) => { return (
- + diff --git a/src/views/HomeView/HomeView.tsx b/src/views/HomeView/HomeView.tsx index 226c710..d3161f0 100644 --- a/src/views/HomeView/HomeView.tsx +++ b/src/views/HomeView/HomeView.tsx @@ -1,22 +1,32 @@ import { type Component } from "solid-js"; -import { PlusIcon, SettingsIcon } from "../../components/icons"; +import { MinusIcon, PlusIcon, SettingsIcon } from "../../icons"; import { HomeCard } from "../../components/HomeCard"; -import { dispatch } from "../../store/state"; -import { AppActionTypes } from "../../store/app"; +import { setAddCommunityOpen, setSettingsOpen } from "../../store/app"; +import { removeSession } from "../../services/session"; +import { useNavigate } from "@solidjs/router"; +import { state } from "../../store/state"; +import { resetLoggedIn, resetAuthSession } from "../../store/auth"; const HomeView: Component = () => { + const navigate = useNavigate(); + const onNewClick = () => { - dispatch({ - type: AppActionTypes.SET_ADD_COMMUNITY_OPEN, - payload: true, - }); + setAddCommunityOpen(true); }; const onSettingsClick = () => { - dispatch({ - type: AppActionTypes.SET_SETTINGS_OPEN, - payload: true, - }); + setSettingsOpen(true); + }; + + const onLogoutClick = () => { + if (state.auth.session?.id) { + removeSession(state.auth.session.id); + } + + resetLoggedIn(); + resetAuthSession(); + + navigate("/"); }; return ( @@ -34,6 +44,12 @@ const HomeView: Component = () => { icon={SettingsIcon} onClick={onSettingsClick} /> +
); diff --git a/src/views/LoginView/LoginView.tsx b/src/views/LoginView/LoginView.tsx index 60905a6..c21f24b 100644 --- a/src/views/LoginView/LoginView.tsx +++ b/src/views/LoginView/LoginView.tsx @@ -1,7 +1,7 @@ import { createEffect, createSignal, onMount, type Component } from "solid-js"; -import { dispatch, state } from "../../store/state"; -import { AuthActionTypes } from "../../store/auth"; +import { state } from "../../store/state"; import { useNavigate } from "@solidjs/router"; +import { fetchLogin, fetchRefresh } from "../../services/auth"; const LoginView: Component = () => { const [getUsername, setUsername] = createSignal(""); @@ -10,19 +10,28 @@ const LoginView: Component = () => { const navigate = useNavigate(); onMount(() => { - dispatch({ - type: AuthActionTypes.FETCH_REFRESH_START, - }); + fetchRefresh(); }); const onLogin = () => { - dispatch({ - type: AuthActionTypes.FETCH_LOGIN_START, - payload: { - username: getUsername(), - password: getPassword(), - }, - }); + if ( + getUsername().trim().length < 1 || + getPassword().trim().length < 1 + ) { + return; + } + + fetchLogin(getUsername(), getPassword()); + }; + + const onRegister = () => { + navigate("/register"); + }; + + const handleEnter = (e: KeyboardEvent) => { + if (e.key === "Enter") { + onLogin(); + } }; createEffect(() => { @@ -32,32 +41,45 @@ const LoginView: Component = () => { }); return ( -
-
- Login - - - setUsername(e.target.value)} - /> - - - setPassword(e.target.value)} - /> - - -
+
+
+
+ Login + + + +
+
+ No Account? + +
+
); }; diff --git a/src/views/MemberView/MemberView.tsx b/src/views/MemberView/MemberView.tsx index 7cba075..6d65218 100644 --- a/src/views/MemberView/MemberView.tsx +++ b/src/views/MemberView/MemberView.tsx @@ -1,8 +1,8 @@ import { createEffect, createMemo, type Component } from "solid-js"; -import { dispatch, state } from "../../store/state"; -import { CommunityActionTypes } from "../../store/community"; -import { UserActionTypes } from "../../store/user"; +import { state } from "../../store/state"; import { Member } from "../../components/Member"; +import { fetchCommunityMembers } from "../../services/community"; +import { fetchUser } from "../../services/user"; const MemberView: Component = () => { const memberIds = createMemo(() => { @@ -21,18 +21,12 @@ const MemberView: Component = () => { createEffect(() => { if (state.community.active) { - dispatch({ - type: CommunityActionTypes.FETCH_COMMUNITY_MEMBERS_START, - payload: state.community.active, - }); + fetchCommunityMembers(state.community.active); } }); const onMemberClick = (id: string) => { - dispatch({ - type: UserActionTypes.FETCH_USER_START, - payload: id, - }); + fetchUser(id); }; const mapMember = (memberId: string) => { diff --git a/src/views/ModalView/ModalView.tsx b/src/views/ModalView/ModalView.tsx index 94eeb6a..cbc82c6 100644 --- a/src/views/ModalView/ModalView.tsx +++ b/src/views/ModalView/ModalView.tsx @@ -1,13 +1,17 @@ import { createEffect, type Component } from "solid-js"; -import { dispatch, state } from "../../store/state"; -import SettingsModal from "../../components/SettingsModal/SettingsModal"; -import CommunityModal from "../../components/CommunityModal/CommunityModal"; -import CommunitySettingsModal from "../../components/CommunitySettingsModal/CommunitySettingsModal"; -import { AppActionTypes } from "../../store/app"; +import { state } from "../../store/state"; +import SettingsModalView from "../SettingsModalView/SettingsModalView"; +import AddCommunityModalView from "../AddCommunityModalView/AddCommunityModalView"; +import CommunitySettingsModalView from "../CommunitySettingsModalView/CommunitySettingsModalView"; +import { + setAddCommunityOpen, + setCommunitySettingsOpen, + setSettingsOpen, +} from "../../store/app"; const ModalView: Component = () => { let settingsModal: HTMLDialogElement; - let communityModal: HTMLDialogElement; + let addCommunityModal: HTMLDialogElement; let communitySettingsModal: HTMLDialogElement; createEffect(() => { @@ -20,9 +24,9 @@ const ModalView: Component = () => { createEffect(() => { if (state.app.dialogsOpen.addCommunityOpen) { - communityModal.showModal(); + addCommunityModal.showModal(); } else { - communityModal.close(); + addCommunityModal.close(); } }); @@ -35,37 +39,28 @@ const ModalView: Component = () => { }); const onCloseSettings = () => { - dispatch({ - type: AppActionTypes.SET_SETTINGS_OPEN, - payload: false, - }); + setSettingsOpen(false); }; - const onCloseCommunity = () => { - dispatch({ - type: AppActionTypes.SET_ADD_COMMUNITY_OPEN, - payload: false, - }); + const onCloseAddCommunity = () => { + setAddCommunityOpen(false); }; const onCloseCommunitySettings = () => { - dispatch({ - type: AppActionTypes.SET_ADD_COMMUNITY_SETTINGS_OPEN, - payload: false, - }); + setCommunitySettingsOpen(false); }; return ( <> - (settingsModal = element)} onClose={onCloseSettings} /> - (communityModal = element)} - onClose={onCloseCommunity} + (addCommunityModal = element)} + onClose={onCloseAddCommunity} /> - (communitySettingsModal = element)} onClose={onCloseCommunitySettings} /> diff --git a/src/views/RegisterView/RegisterView.tsx b/src/views/RegisterView/RegisterView.tsx index 1ec4bd3..69fecc8 100644 --- a/src/views/RegisterView/RegisterView.tsx +++ b/src/views/RegisterView/RegisterView.tsx @@ -1,7 +1,8 @@ import { useNavigate } from "@solidjs/router"; import { createSignal, createEffect, type Component, onMount } from "solid-js"; -import { dispatch, state } from "../../store/state"; -import { AuthActionTypes } from "../../store/auth"; +import { state } from "../../store/state"; +import { fetchRefresh, fetchRegister } from "../../services/auth"; +import { resetRegisterSuccess, setRegisterSuccess } from "../../store/auth"; const RegisterView: Component = () => { const [getUsername, setUsername] = createSignal(""); @@ -11,9 +12,36 @@ const RegisterView: Component = () => { const navigate = useNavigate(); onMount(() => { - dispatch({ - type: AuthActionTypes.FETCH_REFRESH_START, - }); + fetchRefresh(); + }); + + const onRegister = () => { + if ( + getUsername().trim().length < 1 || + getEmail().trim().length < 1 || + getPassword().trim().length < 1 + ) { + return; + } + + fetchRegister(getUsername(), getPassword(), getEmail()); + }; + + const onLogin = () => { + navigate("/login"); + }; + + const handleEnter = (e: KeyboardEvent) => { + if (e.key === "Enter") { + onRegister(); + } + }; + + createEffect(() => { + if (state.auth.registerSuccess === true) { + resetRegisterSuccess(); + navigate("/login"); + } }); createEffect(() => { @@ -22,7 +50,59 @@ const RegisterView: Component = () => { } }); - return
; + return ( +
+
+
+ Register + + + + +
+
+ + Already have an Account? + + +
+
+
+ ); }; export { RegisterView }; diff --git a/src/views/SettingsModalView/SettingsModalView.tsx b/src/views/SettingsModalView/SettingsModalView.tsx new file mode 100644 index 0000000..9467add --- /dev/null +++ b/src/views/SettingsModalView/SettingsModalView.tsx @@ -0,0 +1,69 @@ +import { createSignal, type Component, type JSXElement } from "solid-js"; +import { ISettingsModalViewProps } from "./types"; +import SettingsServersPage from "./pages/SettingsServersPage/SettingsServersPage"; +import SettingsSessionPage from "./pages/SettingsSessionsPage/SettingsSessionsPage"; +import { SettingsItem } from "../../components/SettingsItem"; +import { Dynamic } from "solid-js/web"; + +const SettingsModalView: Component = ( + props: ISettingsModalViewProps, +) => { + const [getSelectedPage, setSelectedPage] = createSignal( + undefined, + ); + + const pages = new Map([ + ["Servers", SettingsServersPage], + ["Sessions", SettingsSessionPage], + ]); + + const getCurrentPage = (): JSXElement => { + const selectedPage = getSelectedPage(); + + if (selectedPage) { + return ; + } else { + return undefined; + } + }; + + const mapPageButton = (page: [string, Component]): JSXElement => ( + + ); + + return ( +
+ + + + +
+ ); +}; + +export default SettingsModalView; diff --git a/src/views/SettingsModalView/index.ts b/src/views/SettingsModalView/index.ts new file mode 100644 index 0000000..df141aa --- /dev/null +++ b/src/views/SettingsModalView/index.ts @@ -0,0 +1,2 @@ +export * from "./SettingsModalView"; +export * from "./types"; diff --git a/src/views/SettingsModalView/pages/SettingsServersPage/SettingsServersPage.tsx b/src/views/SettingsModalView/pages/SettingsServersPage/SettingsServersPage.tsx new file mode 100644 index 0000000..4ded252 --- /dev/null +++ b/src/views/SettingsModalView/pages/SettingsServersPage/SettingsServersPage.tsx @@ -0,0 +1,132 @@ +import { + createMemo, + createSignal, + type Component, + type JSXElement, +} from "solid-js"; +import { Input } from "../../../../components/Input"; +import { SettingsItem } from "../../../../components/SettingsItem"; +import { state } from "../../../../store/state"; +import { + ICommunity, + setCommunityEncryptionKey, +} from "../../../../store/community"; +import { DB_STORE, dbSaveEncrypted } from "../../../../services/database"; +import { fetchChannelMessages } from "../../../../services/channel"; + +const SettingsEncryptionPage: Component = () => { + const [getSelectedCommunityId, setSelectedCommunityId] = createSignal< + string | undefined + >(undefined); + const [getEncryptionKey, setEncryptionKey] = createSignal(""); + + const getCommunity = (): ICommunity | undefined => { + const selectedCommunityId = getSelectedCommunityId(); + if (!selectedCommunityId) { + return; + } + + return state.community.communities[selectedCommunityId]; + }; + + const onSelectCommunity = (id: string) => { + const community = state.community.communities[id]; + if (!community) { + return undefined; + } + + setSelectedCommunityId(id); + + setEncryptionKey(community.encryptionKey ?? ""); + }; + + const onUpdateEncryptionKey = () => { + const community = getCommunity(); + if (!community) { + return; + } + + setCommunityEncryptionKey(community.id, getEncryptionKey()); + dbSaveEncrypted( + DB_STORE.COMMUNITY_ENCRYPTION_KEYS, + community.id, + getEncryptionKey(), + ); + + if (state.channel.active) { + fetchChannelMessages(state.channel.active, community.id); + } + }; + + const communityIds = createMemo(() => { + const loggedUserId = state.user.loggedUserId; + if (!loggedUserId) { + return []; + } + + const loggedUser = state.user.users[loggedUserId]; + if (!loggedUser) { + return []; + } + + return loggedUser.communities ?? []; + }); + + const mapCommunity = (communityId: string): JSXElement => { + const community = state.community.communities[communityId]; + if (!community) { + return undefined; + } + + return ( + + ); + }; + + const optionsHtml = (): JSXElement => { + const community = getCommunity(); + if (!community) { + return undefined; + } + + return ( + <> +

+ {community.name} +

+

+ End-to-end encryption key +

+ + + ); + }; + + return ( +
+
+

Servers

+ {communityIds().map(mapCommunity)} +
+
+
{optionsHtml()}
+
+ ); +}; + +export default SettingsEncryptionPage; diff --git a/src/views/SettingsModalView/pages/SettingsServersPage/index.ts b/src/views/SettingsModalView/pages/SettingsServersPage/index.ts new file mode 100644 index 0000000..e952e5b --- /dev/null +++ b/src/views/SettingsModalView/pages/SettingsServersPage/index.ts @@ -0,0 +1 @@ +export * from "./SettingsServersPage"; diff --git a/src/views/SettingsModalView/pages/SettingsSessionsPage/SettingsSessionsPage.tsx b/src/views/SettingsModalView/pages/SettingsSessionsPage/SettingsSessionsPage.tsx new file mode 100644 index 0000000..d1bbc05 --- /dev/null +++ b/src/views/SettingsModalView/pages/SettingsSessionsPage/SettingsSessionsPage.tsx @@ -0,0 +1,125 @@ +import { + createMemo, + createSignal, + JSXElement, + onMount, + type Component, +} from "solid-js"; +import { fetchUserSessions } from "../../../../services/user"; +import { state } from "../../../../store/state"; +import { RichSettingsItem } from "../../../../components/RichSettingsItem"; +import { DeviceIcon, MinusIcon, TrashIcon } from "../../../../icons"; +import { removeSession } from "../../../../services/session"; +import { useNavigate } from "@solidjs/router"; + +const SettingsSessionPage: Component = () => { + const [getSelectedSessionId, setSelectedSessionId] = createSignal< + string | undefined + >(undefined); + + const navigate = useNavigate(); + + onMount(() => { + if (state.user.loggedUserId) { + fetchUserSessions(state.user.loggedUserId); + } + }); + + const sessionIds = createMemo(() => { + const loggedUserId = state.user.loggedUserId; + if (!loggedUserId) { + return []; + } + + const loggedUser = state.user.users[loggedUserId]; + if (!loggedUser) { + return []; + } + + return loggedUser.sessions ?? []; + }); + + const onRevokeSession = (sessionId: string) => { + removeSession(sessionId); + + if (sessionId === state.auth.session?.id) { + navigate("/"); + } + }; + + const mapSession = (sessionId: string): JSXElement => { + const session = state.session.sessions[sessionId]; + if (!session) { + return undefined; + } + + const isCurrentSession = session.id === state.auth.session?.id; + + return ( + +
+

+ Session ID:{" "} + {session.id} +

+

+ Session name:{" "} + + {session.name ?? "unknown"} + +

+

+ Session user agent:{" "} + + {session.userAgent ?? "unknown"} + +

+

+ First appeared:{" "} + + {session.creationDate + ? new Date( + session.creationDate, + ).toLocaleString() + : "unknown"} + +

+

+ Last active:{" "} + + {session.refreshDate + ? new Date(session.refreshDate).toLocaleString() + : "unknown"} + +

+ +
+
+ ); + }; + + return ( +
+ {sessionIds().map(mapSession)} +
+ ); +}; + +export default SettingsSessionPage; diff --git a/src/views/SettingsModalView/pages/SettingsSessionsPage/index.ts b/src/views/SettingsModalView/pages/SettingsSessionsPage/index.ts new file mode 100644 index 0000000..c1e680f --- /dev/null +++ b/src/views/SettingsModalView/pages/SettingsSessionsPage/index.ts @@ -0,0 +1 @@ +export * from "./SettingsSessionsPage"; diff --git a/src/components/SettingsModal/types.ts b/src/views/SettingsModalView/types.ts similarity index 51% rename from src/components/SettingsModal/types.ts rename to src/views/SettingsModalView/types.ts index 4a0d823..58f470c 100644 --- a/src/components/SettingsModal/types.ts +++ b/src/views/SettingsModalView/types.ts @@ -1,6 +1,6 @@ -interface ISettingsModalProps { +interface ISettingsModalViewProps { dialogRef?: (element: HTMLDialogElement) => void; onClose?: () => void; } -export { type ISettingsModalProps }; +export { type ISettingsModalViewProps }; diff --git a/src/views/StartView/StartView.tsx b/src/views/StartView/StartView.tsx index 7aed013..a2eae21 100644 --- a/src/views/StartView/StartView.tsx +++ b/src/views/StartView/StartView.tsx @@ -1,7 +1,38 @@ +import { useNavigate } from "@solidjs/router"; import { type Component } from "solid-js"; const StartView: Component = () => { - return
; + const navigate = useNavigate(); + + const onLogin = () => { + navigate("/login"); + }; + + const onRegister = () => { + navigate("/register"); + }; + + return ( +
+
+
+ Pulsar Web + + +
+
+
+ ); }; export { StartView };