add joinable rooms on opengroupv2 joining screen

pull/1576/head
Audric Ackermann 5 years ago
parent 6aa699ad23
commit 5289d4c2aa
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -7,7 +7,7 @@ import is from '@sindresorhus/is';
import * as GoogleChrome from '../util/GoogleChrome'; import * as GoogleChrome from '../util/GoogleChrome';
import * as MIME from '../types/MIME'; import * as MIME from '../types/MIME';
import { SessionIconButton, SessionIconSize, SessionIconType } from './session/icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from './session/icon';
import { Flex } from './session/Flex'; import { Flex } from './basic/Flex';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
// useCss has some issues on our setup. so import it directly // useCss has some issues on our setup. so import it directly
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports

@ -0,0 +1,29 @@
import React from 'react';
import styled from 'styled-components';
type PillContainerProps = {
children: React.ReactNode;
margin?: string;
padding?: string;
onClick?: () => void;
};
const StyledPillContainer = styled.div<PillContainerProps>`
display: flex;
background: none;
flex-direction: 'row';
flex-grow: 1;
align-items: center;
padding: ${props => props.padding || ''};
margin: ${props => props.margin || ''};
border-radius: 300px;
border: 1px solid ${props => props.theme.colors.pillDividerColor};
transition: ${props => props.theme.common.animations.defaultDuration};
&:hover {
background: ${props => props.theme.colors.clickableHovered};
}
`;
export const PillContainer = (props: PillContainerProps) => {
return <StyledPillContainer {...props}>{props.children}</StyledPillContainer>;
};

@ -0,0 +1,23 @@
import React from 'react';
import styled from 'styled-components';
type TextProps = {
text: string;
subtle?: boolean;
opposite?: boolean;
};
const StyledDefaultText = styled.div<TextProps>`
transition: ${props => props.theme.common.animations.defaultDuration};
font-family: ${props => props.theme.common.fonts.sessionFontDefault};
color: ${props =>
props.opposite
? props.theme.colors.textColorOpposite
: props.subtle
? props.theme.colors.textColorSubtle
: props.theme.colors.textColor};
`;
export const Text = (props: TextProps) => {
return <StyledDefaultText {...props}>{props.text}</StyledDefaultText>;
};

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Spinner } from '../Spinner'; import { Spinner } from '../basic/Spinner';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
import { AttachmentType } from '../../types/Attachment'; import { AttachmentType } from '../../types/Attachment';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';

@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Avatar, AvatarSize } from '../Avatar'; import { Avatar, AvatarSize } from '../Avatar';
import { Spinner } from '../Spinner'; import { Spinner } from '../basic/Spinner';
import { MessageBody } from './MessageBody'; import { MessageBody } from './MessageBody';
import { ImageGrid } from './ImageGrid'; import { ImageGrid } from './ImageGrid';
import { Image } from './Image'; import { Image } from './Image';

@ -5,7 +5,7 @@ import { ToastUtils } from '../../session/utils';
import { SessionModal } from '../session/SessionModal'; import { SessionModal } from '../session/SessionModal';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
import { SessionSpinner } from '../session/SessionSpinner'; import { SessionSpinner } from '../session/SessionSpinner';
import { Flex } from '../session/Flex'; import { Flex } from '../basic/Flex';
import { ConversationModel } from '../../models/conversation'; import { ConversationModel } from '../../models/conversation';
interface Props { interface Props {
convo: ConversationModel; convo: ConversationModel;

@ -5,7 +5,7 @@ import { ApiV2 } from '../../opengroup/opengroupV2';
import { ConversationController } from '../../session/conversations'; import { ConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types'; import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils'; import { ToastUtils } from '../../session/utils';
import { Flex } from '../session/Flex'; import { Flex } from '../basic/Flex';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem'; import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem';
import { SessionModal } from '../session/SessionModal'; import { SessionModal } from '../session/SessionModal';

@ -3,7 +3,7 @@ import classNames from 'classnames';
import { MessageSendingErrorText, MetadataSpacer } from './MetadataUtilComponent'; import { MessageSendingErrorText, MetadataSpacer } from './MetadataUtilComponent';
import { OutgoingMessageStatus } from './OutgoingMessageStatus'; import { OutgoingMessageStatus } from './OutgoingMessageStatus';
import { Spinner } from '../../Spinner'; import { Spinner } from '../../basic/Spinner';
import { MetadataBadges } from './MetadataBadge'; import { MetadataBadges } from './MetadataBadge';
import { Timestamp } from '../Timestamp'; import { Timestamp } from '../Timestamp';
import { ExpireTimer } from '../ExpireTimer'; import { ExpireTimer } from '../ExpireTimer';

@ -31,6 +31,7 @@ import { showLeftPaneSection } from '../../state/ducks/section';
import { cleanUpOldDecryptedMedias } from '../../session/crypto/DecryptedAttachmentsManager'; import { cleanUpOldDecryptedMedias } from '../../session/crypto/DecryptedAttachmentsManager';
import { OpenGroupManagerV2 } from '../../opengroup/opengroupV2/OpenGroupManagerV2'; import { OpenGroupManagerV2 } from '../../opengroup/opengroupV2/OpenGroupManagerV2';
import { loadDefaultRoomsIfNeeded } from '../../opengroup/opengroupV2/ApiUtil';
// tslint:disable-next-line: no-import-side-effect no-submodule-imports // tslint:disable-next-line: no-import-side-effect no-submodule-imports
export enum SectionType { export enum SectionType {
@ -175,11 +176,10 @@ export const ActionsPanel = () => {
}; };
void generateAttachmentKeyIfEmpty(); void generateAttachmentKeyIfEmpty();
// trigger a sync message if needed for our other devices // trigger a sync message if needed for our other devices
// 'http://sessionopengroup.com/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b'
// 'https://sog.ibolpap.finance/main?public_key=b464aa186530c97d6bcf663a3a3b7465a5f782beaa67c83bee99468824b4aa10'
// 'https://opengroup.bilb.us/main?public_key=1352534ba73d4265973280431dbc72e097a3e43275d1ada984f9805b4943047d'
void OpenGroupManagerV2.getInstance().startPolling(); void OpenGroupManagerV2.getInstance().startPolling();
void syncConfiguration(); void syncConfiguration();
void loadDefaultRoomsIfNeeded();
}, []); }, []);
// wait for cleanUpMediasInterval and then start cleaning up medias // wait for cleanUpMediasInterval and then start cleaning up medias

@ -12,6 +12,7 @@ import { PillDivider } from './PillDivider';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
import { UserUtils } from '../../session/utils'; import { UserUtils } from '../../session/utils';
import { ConversationTypeEnum } from '../../models/conversation'; import { ConversationTypeEnum } from '../../models/conversation';
import { SessionJoinableRooms } from './SessionJoinableDefaultRooms';
export enum SessionClosableOverlayType { export enum SessionClosableOverlayType {
Message = 'message', Message = 'message',
@ -220,6 +221,7 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
{descriptionLong && <div className="session-description-long">{descriptionLong}</div>} {descriptionLong && <div className="session-description-long">{descriptionLong}</div>}
{isMessageView && false && <h4>{window.i18n('or')}</h4>} {isMessageView && false && <h4>{window.i18n('or')}</h4>}
{/* FIXME enable back those two items when they are working */} {/* FIXME enable back those two items when they are working */}
{isOpenGroupView && <SessionJoinableRooms />}
{isMessageView && false && ( {isMessageView && false && (
<UserSearchDropdown <UserSearchDropdown
searchTerm={searchTerm || ''} searchTerm={searchTerm || ''}

@ -0,0 +1,70 @@
import React, { useReducer } from 'react';
import { useSelector } from 'react-redux';
import { joinOpenGroupV2WithUIEvents } from '../../opengroup/opengroupV2/JoinOpenGroupV2';
import { StateType } from '../../state/reducer';
import { Avatar, AvatarSize } from '../Avatar';
import { Flex } from '../basic/Flex';
import { PillContainer } from '../basic/PillContainer';
// tslint:disable: no-void-expression
export type JoinableRoomProps = {
completeUrl: string;
name: string;
imageId?: string;
onClick: (completeUrl: string) => void;
};
const SessionJoinableRoomAvatar = (props: JoinableRoomProps) => {
return (
<Avatar
size={AvatarSize.XS}
{...props}
onAvatarClick={() => props.onClick(props.completeUrl)}
/>
);
};
const SessionJoinableRoomName = (props: JoinableRoomProps) => {
return <Flex padding="0 10px">{props.name}</Flex>;
};
const SessionJoinableRoomRow = (props: JoinableRoomProps) => {
return (
<PillContainer
onClick={() => {
props.onClick(props.completeUrl);
}}
margin="5px"
padding="5px"
>
<SessionJoinableRoomAvatar {...props} />
<SessionJoinableRoomName {...props} />
</PillContainer>
);
};
export const SessionJoinableRooms = () => {
const joinableRooms = useSelector((state: StateType) => state.defaultRooms);
if (!joinableRooms?.length) {
console.warn('no default joinable rooms yet');
return <></>;
}
return (
<Flex container={true} flexGrow={1} flexWrap="wrap">
{joinableRooms.map(r => {
return (
<SessionJoinableRoomRow
key={r.id}
completeUrl={r.completeUrl}
name={r.name}
onClick={completeUrl => {
void joinOpenGroupV2WithUIEvents(completeUrl, true);
}}
/>
);
})}
</Flex>
);
};

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Flex } from './Flex'; import { Flex } from '../basic/Flex';
// tslint:disable: react-unused-props-and-state // tslint:disable: react-unused-props-and-state
interface Props { interface Props {

@ -1,7 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon/'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon/';
import { Flex } from './Flex'; import { Flex } from '../basic/Flex';
import styled, { ThemeContext } from 'styled-components'; import styled, { ThemeContext } from 'styled-components';
import { noop } from 'lodash'; import { noop } from 'lodash';

@ -13,7 +13,7 @@ import { SignalService } from '../../../protobuf';
import { Constants } from '../../../session'; import { Constants } from '../../../session';
import { toArray } from 'react-emoji-render'; import { toArray } from 'react-emoji-render';
import { Flex } from '../Flex'; import { Flex } from '../../basic/Flex';
import { AttachmentList } from '../../conversation/AttachmentList'; import { AttachmentList } from '../../conversation/AttachmentList';
import { ToastUtils } from '../../../session/utils'; import { ToastUtils } from '../../../session/utils';
import { AttachmentUtil } from '../../../util'; import { AttachmentUtil } from '../../../util';

@ -1,6 +1,6 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import styled, { ThemeContext } from 'styled-components'; import styled, { ThemeContext } from 'styled-components';
import { Flex } from '../Flex'; import { Flex } from '../../basic/Flex';
import { SessionIcon, SessionIconSize, SessionIconType } from '../icon'; import { SessionIcon, SessionIconSize, SessionIconType } from '../icon';
// padding-inline-end: ${props => props.theme.common.margins.md}; // padding-inline-end: ${props => props.theme.common.margins.md};

@ -1,5 +1,5 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Flex } from '../Flex'; import { Flex } from '../../basic/Flex';
import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { ReplyingToMessageProps } from './SessionCompositionBox'; import { ReplyingToMessageProps } from './SessionCompositionBox';
import styled, { DefaultTheme, ThemeContext } from 'styled-components'; import styled, { DefaultTheme, ThemeContext } from 'styled-components';

@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Flex } from '../Flex'; import { Flex } from '../../basic/Flex';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionSpinner } from '../SessionSpinner'; import { SessionSpinner } from '../SessionSpinner';
import { signInWithLinking, signInWithRecovery, validatePassword } from './RegistrationTabs'; import { signInWithLinking, signInWithRecovery, validatePassword } from './RegistrationTabs';

@ -1,6 +1,11 @@
import _ from 'underscore'; import _ from 'underscore';
import { PubKey } from '../../session/types'; import { PubKey } from '../../session/types';
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
import { fromBase64ToArrayBuffer, fromHex } from '../../session/utils/String'; import { fromBase64ToArrayBuffer, fromHex } from '../../session/utils/String';
import { updateDefaultRooms } from '../../state/ducks/defaultRooms';
import { getCompleteUrlFromRoom } from '../utils/OpenGroupUtils';
import { parseOpenGroupV2 } from './JoinOpenGroupV2';
import { getAllRoomInfos } from './OpenGroupAPIV2';
import { OpenGroupMessageV2 } from './OpenGroupMessageV2'; import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
export const defaultServer = 'https://sessionopengroup.com'; export const defaultServer = 'https://sessionopengroup.com';
@ -36,6 +41,13 @@ export type OpenGroupV2Info = {
imageId?: string; imageId?: string;
}; };
export type OpenGroupV2InfoJoinable = {
id: string;
name: string;
completeUrl: string;
imageId?: string;
};
/** /**
* Try to build an full url and check it for validity. * Try to build an full url and check it for validity.
* @returns null if the check failed. the built URL otherwise * @returns null if the check failed. the built URL otherwise
@ -97,3 +109,53 @@ export const parseMessages = async (
); );
return _.compact(messages); return _.compact(messages);
}; };
// 'http://sessionopengroup.com/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b'
// FIXME audric change this to sessionopengroup.com once http is fixed
const defaultRoom =
'https://opengroup.bilb.us/main?public_key=1352534ba73d4265973280431dbc72e097a3e43275d1ada984f9805b4943047d';
const loadDefaultRoomsSingle = () =>
allowOnlyOneAtATime(
'loadDefaultRoomsSingle',
async (): Promise<Array<OpenGroupV2InfoJoinable>> => {
const roomInfos = parseOpenGroupV2(defaultRoom);
if (roomInfos) {
try {
const roomsGot = await getAllRoomInfos(roomInfos);
if (!roomsGot) {
return [];
}
return roomsGot.map(room => {
return {
...room,
completeUrl: getCompleteUrlFromRoom({
serverUrl: roomInfos.serverUrl,
serverPublicKey: roomInfos.serverPublicKey,
roomId: room.id,
}),
};
});
} catch (e) {
window.log.warn('loadDefaultRoomsIfNeeded failed', e);
}
return [];
}
return [];
}
);
/**
* Load to the cache all the details of the room of the default opengroupv2 server
* This call will only run once at a time.
*/
export const loadDefaultRoomsIfNeeded = async () => {
// FIXME audric do the UI and refresh this list from time to time
const allRooms: Array<OpenGroupV2InfoJoinable> = await loadDefaultRoomsSingle();
if (allRooms !== undefined) {
window.inboxStore?.dispatch(updateDefaultRooms(allRooms));
}
};

@ -27,6 +27,7 @@ export function parseOpenGroupV2(urlWithPubkey: string): OpenGroupV2Room | undef
if (!openGroupV2CompleteURLRegex.test(lowerCased)) { if (!openGroupV2CompleteURLRegex.test(lowerCased)) {
throw new Error('regex fail'); throw new Error('regex fail');
} }
// prefix the URL if it does not have a prefix // prefix the URL if it does not have a prefix
const prefixedUrl = prefixify(lowerCased); const prefixedUrl = prefixify(lowerCased);
// new URL fails if the protocol is not explicit // new URL fails if the protocol is not explicit
@ -44,7 +45,7 @@ export function parseOpenGroupV2(urlWithPubkey: string): OpenGroupV2Room | undef
}; };
return room; return room;
} catch (e) { } catch (e) {
window.log.error('Invalid Opengroup v2 join URL:', lowerCased); window.log.error('Invalid Opengroup v2 join URL:', lowerCased, e);
} }
return undefined; return undefined;
} }
@ -127,14 +128,14 @@ export async function joinOpenGroupV2WithUIEvents(
showToasts: boolean, showToasts: boolean,
uiCallback?: (loading: boolean) => void uiCallback?: (loading: boolean) => void
): Promise<boolean> { ): Promise<boolean> {
const parsedRoom = parseOpenGroupV2(completeUrl);
if (!parsedRoom) {
if (showToasts) {
ToastUtils.pushToastError('connectToServer', window.i18n('invalidOpenGroupUrl'));
}
return false;
}
try { try {
const parsedRoom = parseOpenGroupV2(completeUrl);
if (!parsedRoom) {
if (showToasts) {
ToastUtils.pushToastError('connectToServer', window.i18n('invalidOpenGroupUrl'));
}
return false;
}
const conversationID = getOpenGroupV2ConversationId(parsedRoom.serverUrl, parsedRoom.roomId); const conversationID = getOpenGroupV2ConversationId(parsedRoom.serverUrl, parsedRoom.roomId);
if (ConversationController.getInstance().get(conversationID)) { if (ConversationController.getInstance().get(conversationID)) {
if (showToasts) { if (showToasts) {

@ -20,9 +20,15 @@ export const openGroupV2ServerUrlRegex = new RegExp(
`${protocolRegex.source}${hostnameRegex.source}${portRegex}` `${protocolRegex.source}${hostnameRegex.source}${portRegex}`
); );
/**
* Regex to use to check if a string is a v2open completeURL with pubkey.
* Be aware that the /g flag is not set as .test() will otherwise return alternating result
*
* @see https://stackoverflow.com/a/9275499/1680951
*/
export const openGroupV2CompleteURLRegex = new RegExp( export const openGroupV2CompleteURLRegex = new RegExp(
`^${openGroupV2ServerUrlRegex.source}\/${roomIdV2Regex}${qMark}${publicKeyParam}${publicKeyRegex}$`, `${openGroupV2ServerUrlRegex.source}\/${roomIdV2Regex}${qMark}${publicKeyParam}${publicKeyRegex}`,
'gm' 'm'
); );
/** /**

@ -6,13 +6,15 @@ import { SessionToast, SessionToastType } from '../../components/session/Session
// if you push a toast manually with toast...() be sure to set the type attribute of the SessionToast component // if you push a toast manually with toast...() be sure to set the type attribute of the SessionToast component
export function pushToastError(id: string, title: string, description?: string) { export function pushToastError(id: string, title: string, description?: string) {
toast.error( toast.error(
<SessionToast title={title} description={description} type={SessionToastType.Error} /> <SessionToast title={title} description={description} type={SessionToastType.Error} />,
{ toastId: id, updateId: id }
); );
} }
export function pushToastWarning(id: string, title: string, description?: string) { export function pushToastWarning(id: string, title: string, description?: string) {
toast.warning( toast.warning(
<SessionToast title={title} description={description} type={SessionToastType.Warning} /> <SessionToast title={title} description={description} type={SessionToastType.Warning} />,
{ toastId: id, updateId: id }
); );
} }
@ -28,7 +30,8 @@ export function pushToastInfo(
description={description} description={description}
type={SessionToastType.Info} type={SessionToastType.Info}
onToastClick={onToastClick} onToastClick={onToastClick}
/> />,
{ toastId: id, updateId: id }
); );
} }
@ -44,7 +47,8 @@ export function pushToastSuccess(
description={description} description={description}
type={SessionToastType.Success} type={SessionToastType.Success}
icon={icon} icon={icon}
/> />,
{ toastId: id, updateId: id }
); );
} }

@ -0,0 +1,24 @@
import { createSlice } from '@reduxjs/toolkit';
import { OpenGroupV2InfoJoinable } from '../../opengroup/opengroupV2/ApiUtil';
export type DefaultRoomsState = Array<OpenGroupV2InfoJoinable>;
const initialState: DefaultRoomsState = [];
/**
* This slice is the one holding the default joinable rooms fetched once in a while from the default opengroup v2 server.
*/
const defaultRoomsSlice = createSlice({
name: 'defaultRooms',
initialState,
reducers: {
updateDefaultRooms(state, action) {
window.log.warn('updating default rooms', action.payload);
return action.payload as DefaultRoomsState;
},
},
});
const { actions, reducer } = defaultRoomsSlice;
export const { updateDefaultRooms } = actions;
export const defaultRoomReducer = reducer;

@ -5,6 +5,7 @@ import { ConversationsStateType, reducer as conversations } from './ducks/conver
import { reducer as user, UserStateType } from './ducks/user'; import { reducer as user, UserStateType } from './ducks/user';
import { reducer as theme, ThemeStateType } from './ducks/theme'; import { reducer as theme, ThemeStateType } from './ducks/theme';
import { reducer as section, SectionStateType } from './ducks/section'; import { reducer as section, SectionStateType } from './ducks/section';
import { defaultRoomReducer as defaultRooms, DefaultRoomsState } from './ducks/defaultRooms';
export type StateType = { export type StateType = {
search: SearchStateType; search: SearchStateType;
@ -13,6 +14,7 @@ export type StateType = {
conversations: ConversationsStateType; conversations: ConversationsStateType;
theme: ThemeStateType; theme: ThemeStateType;
section: SectionStateType; section: SectionStateType;
defaultRooms: DefaultRoomsState;
}; };
export const reducers = { export const reducers = {
@ -24,6 +26,7 @@ export const reducers = {
user, user,
theme, theme,
section, section,
defaultRooms,
}; };
// Making this work would require that our reducer signature supported AnyAction, not // Making this work would require that our reducer signature supported AnyAction, not

Loading…
Cancel
Save