Merge pull request #2795 from Bilb/fix-userconfig

fix: bump libsession to allow createdAt to be given during migration
pull/2799/head
Audric Ackermann 2 years ago committed by GitHub
commit 60de4bebbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -70,9 +70,10 @@
"show": "Show", "show": "Show",
"sessionMessenger": "Session", "sessionMessenger": "Session",
"noSearchResults": "No results found for \"$searchTerm$\"", "noSearchResults": "No results found for \"$searchTerm$\"",
"conversationsHeader": "Contacts and Groups", "conversationsHeader": "Contacts and Groups: $count$",
"contactsHeader": "Contacts", "contactsHeader": "Contacts",
"messagesHeader": "Conversations", "messagesHeader": "Conversations",
"searchMessagesHeader": "Messages: $count$",
"settingsHeader": "Settings", "settingsHeader": "Settings",
"typingAlt": "Typing animation for this conversation", "typingAlt": "Typing animation for this conversation",
"contactAvatarAlt": "Avatar for contact $name$", "contactAvatarAlt": "Avatar for contact $name$",

@ -36,6 +36,7 @@
}, },
"scripts": { "scripts": {
"start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod$MULTI electron .", "start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod$MULTI electron .",
"start-dev": "cross-env NODE_ENV=development NODE_APP_INSTANCE=devprod$MULTI electron .",
"build-everything": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && tsc && yarn build:workers", "build-everything": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && tsc && yarn build:workers",
"build-everything:watch": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && yarn build:workers && tsc -w", "build-everything:watch": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && yarn build:workers && tsc -w",
"build:workers": "yarn worker:utils && yarn worker:libsession", "build:workers": "yarn worker:utils && yarn worker:libsession",
@ -74,6 +75,7 @@
"@emoji-mart/data": "^1.0.6", "@emoji-mart/data": "^1.0.6",
"@emoji-mart/react": "^1.0.1", "@emoji-mart/react": "^1.0.1",
"@reduxjs/toolkit": "1.8.5", "@reduxjs/toolkit": "1.8.5",
"@types/react-mentions": "^4.1.8",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"auto-bind": "^4.0.0", "auto-bind": "^4.0.0",
"backbone": "1.3.3", "backbone": "1.3.3",
@ -100,7 +102,7 @@
"glob": "7.1.2", "glob": "7.1.2",
"image-type": "^4.1.0", "image-type": "^4.1.0",
"ip2country": "1.0.1", "ip2country": "1.0.1",
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.19/libsession_util_nodejs-v0.1.19.tar.gz", "libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.20/libsession_util_nodejs-v0.1.20.tar.gz",
"libsodium-wrappers-sumo": "^0.7.9", "libsodium-wrappers-sumo": "^0.7.9",
"linkify-it": "3.0.2", "linkify-it": "3.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -121,7 +123,7 @@
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"react-h5-audio-player": "^3.2.0", "react-h5-audio-player": "^3.2.0",
"react-intersection-observer": "^8.30.3", "react-intersection-observer": "^8.30.3",
"react-mentions": "^4.2.0", "react-mentions": "^4.4.9",
"react-portal": "^4.2.0", "react-portal": "^4.2.0",
"react-qr-svg": "^2.2.1", "react-qr-svg": "^2.2.1",
"react-redux": "8.0.4", "react-redux": "8.0.4",
@ -168,7 +170,6 @@
"@types/rc-slider": "^8.6.5", "@types/rc-slider": "^8.6.5",
"@types/react": "^17.0.2", "@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2", "@types/react-dom": "^17.0.2",
"@types/react-mentions": "^4.1.1",
"@types/react-mic": "^12.4.1", "@types/react-mic": "^12.4.1",
"@types/react-portal": "^4.0.2", "@types/react-portal": "^4.0.2",
"@types/react-redux": "^7.1.24", "@types/react-redux": "^7.1.24",

@ -20,14 +20,6 @@ window.getEnvironment = () => config.environment;
window.getVersion = () => config.version; window.getVersion = () => config.version;
window.getAppInstance = () => config.appInstance; window.getAppInstance = () => config.appInstance;
const { SessionPasswordPrompt } = require('./ts/components/SessionPasswordPrompt');
window.Signal = {
Components: {
SessionPasswordPrompt,
},
};
window.clearLocalData = async () => { window.clearLocalData = async () => {
window.log.info('reset database'); window.log.info('reset database');
ipcRenderer.send('resetDatabase'); ipcRenderer.send('resetDatabase');

@ -17,12 +17,10 @@ import {
import { StateType } from '../../state/reducer'; import { StateType } from '../../state/reducer';
import { import {
getQuotedMessageToAnimate, getQuotedMessageToAnimate,
getSelectedConversation,
getSortedMessagesOfSelectedConversation, getSortedMessagesOfSelectedConversation,
} from '../../state/selectors/conversations'; } from '../../state/selectors/conversations';
import { import { getSelectedConversationKey } from '../../state/selectors/selectedConversation';
getSelectedConversation,
getSelectedConversationKey,
} from '../../state/selectors/selectedConversation';
import { SessionMessagesList } from './SessionMessagesList'; import { SessionMessagesList } from './SessionMessagesList';
import { TypingBubble } from './TypingBubble'; import { TypingBubble } from './TypingBubble';
import { ConversationMessageRequestButtons } from './MessageRequestButtons'; import { ConversationMessageRequestButtons } from './MessageRequestButtons';

@ -31,7 +31,11 @@ import { ToastUtils } from '../../../session/utils';
import { ReduxConversationType } from '../../../state/ducks/conversations'; import { ReduxConversationType } from '../../../state/ducks/conversations';
import { removeAllStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments'; import { removeAllStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
import { StateType } from '../../../state/reducer'; import { StateType } from '../../../state/reducer';
import { getMentionsInput, getQuotedMessage } from '../../../state/selectors/conversations'; import {
getMentionsInput,
getQuotedMessage,
getSelectedConversation,
} from '../../../state/selectors/conversations';
import { AttachmentUtil } from '../../../util'; import { AttachmentUtil } from '../../../util';
import { Flex } from '../../basic/Flex'; import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor'; import { CaptionEditor } from '../../CaptionEditor';
@ -53,7 +57,6 @@ import styled from 'styled-components';
import { FixedBaseEmoji } from '../../../types/Reaction'; import { FixedBaseEmoji } from '../../../types/Reaction';
import { import {
getSelectedCanWrite, getSelectedCanWrite,
getSelectedConversation,
getSelectedConversationKey, getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation'; } from '../../../state/selectors/selectedConversation';
import { SettingsKey } from '../../../data/settings-key'; import { SettingsKey } from '../../../data/settings-key';

@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { SectionType } from '../../state/ducks/section'; import { SectionType } from '../../state/ducks/section';
import { getLeftPaneConversationIds } from '../../state/selectors/conversations'; import { getLeftPaneConversationIds } from '../../state/selectors/conversations';
import { getSearchResultsIdsOnly, isSearching } from '../../state/selectors/search'; import { getHasSearchResults } from '../../state/selectors/search';
import { getFocusedSection, getOverlayMode } from '../../state/selectors/section'; import { getFocusedSection, getOverlayMode } from '../../state/selectors/section';
import { SessionTheme } from '../../themes/SessionTheme'; import { SessionTheme } from '../../themes/SessionTheme';
import { SessionToastContainer } from '../SessionToastContainer'; import { SessionToastContainer } from '../SessionToastContainer';
@ -22,18 +22,14 @@ const StyledLeftPane = styled.div`
`; `;
const InnerLeftPaneMessageSection = () => { const InnerLeftPaneMessageSection = () => {
const showSearch = useSelector(isSearching); const hasSearchResults = useSelector(getHasSearchResults);
const searchResults = useSelector(getSearchResultsIdsOnly);
const conversationIds = useSelector(getLeftPaneConversationIds); const conversationIds = useSelector(getLeftPaneConversationIds);
const overlayMode = useSelector(getOverlayMode); const overlayMode = useSelector(getOverlayMode);
return ( return (
// tslint:disable-next-line: use-simple-attributes
<LeftPaneMessageSection <LeftPaneMessageSection
conversationIds={showSearch ? undefined : conversationIds || []} hasSearchResults={hasSearchResults}
searchResults={showSearch ? searchResults : undefined} conversationIds={conversationIds}
overlayMode={overlayMode} overlayMode={overlayMode}
/> />
); );

@ -1,7 +1,7 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import React from 'react'; import React from 'react';
import { AutoSizer, List, ListRowProps } from 'react-virtualized'; import { AutoSizer, List, ListRowProps } from 'react-virtualized';
import { SearchResults, SearchResultsProps } from '../search/SearchResults'; import { SearchResults } from '../search/SearchResults';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader'; import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
import { MessageRequestsBanner } from './MessageRequestsBanner'; import { MessageRequestsBanner } from './MessageRequestsBanner';
@ -21,7 +21,7 @@ import { assertUnreachable } from '../../types/sqlSharedTypes';
export interface Props { export interface Props {
conversationIds?: Array<string>; conversationIds?: Array<string>;
searchResults?: SearchResultsProps; hasSearchResults: boolean;
overlayMode: OverlayMode | undefined; overlayMode: OverlayMode | undefined;
} }
@ -88,10 +88,10 @@ export class LeftPaneMessageSection extends React.Component<Props> {
}; };
public renderList(): JSX.Element { public renderList(): JSX.Element {
const { conversationIds, searchResults } = this.props; const { conversationIds, hasSearchResults } = this.props;
if (searchResults) { if (hasSearchResults) {
return <SearchResults {...searchResults} />; return <SearchResults />;
} }
if (!conversationIds) { if (!conversationIds) {

@ -6,7 +6,7 @@ import { ContactName } from '../conversation/ContactName';
import { Avatar, AvatarSize } from '../avatar/Avatar'; import { Avatar, AvatarSize } from '../avatar/Avatar';
import { Timestamp } from '../conversation/Timestamp'; import { Timestamp } from '../conversation/Timestamp';
import { MessageBodyHighlight } from '../basic/MessageBodyHighlight'; import { MessageBodyHighlight } from '../basic/MessageBodyHighlight';
import styled from 'styled-components'; import styled, { CSSProperties } from 'styled-components';
import { MessageAttributes } from '../../models/messageType'; import { MessageAttributes } from '../../models/messageType';
import { useConversationUsername, useIsPrivate } from '../../hooks/useParamSelector'; import { useConversationUsername, useIsPrivate } from '../../hooks/useParamSelector';
import { UserUtils } from '../../session/utils'; import { UserUtils } from '../../session/utils';
@ -172,7 +172,9 @@ const StyledTimestampContaimer = styled.div`
color: var(--conversation-tab-text-color); color: var(--conversation-tab-text-color);
`; `;
export const MessageSearchResult = (props: MessageResultProps) => { type MessageSearchResultProps = MessageResultProps & { style: CSSProperties };
export const MessageSearchResult = (props: MessageSearchResultProps) => {
const { const {
id, id,
conversationId, conversationId,
@ -183,6 +185,7 @@ export const MessageSearchResult = (props: MessageResultProps) => {
serverTimestamp, serverTimestamp,
timestamp, timestamp,
direction, direction,
style,
} = props; } = props;
/** destination is only used for search results (showing the `from:` and `to`) /** destination is only used for search results (showing the `from:` and `to`)
@ -210,6 +213,7 @@ export const MessageSearchResult = (props: MessageResultProps) => {
return ( return (
<StyledSearchResults <StyledSearchResults
key={`div-msg-searchresult-${id}`} key={`div-msg-searchresult-${id}`}
style={style}
role="button" role="button"
onClick={() => { onClick={() => {
void openConversationToSpecificMessage({ void openConversationToSpecificMessage({

@ -1,13 +1,16 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled, { CSSProperties } from 'styled-components';
import { ConversationListItem } from '../leftpane/conversation-list-item/ConversationListItem'; import { ConversationListItem } from '../leftpane/conversation-list-item/ConversationListItem';
import { MessageResultProps, MessageSearchResult } from './MessageSearchResults'; import { MessageSearchResult } from './MessageSearchResults';
import { AutoSizer, List } from 'react-virtualized';
export type SearchResultsProps = { import { useSelector } from 'react-redux';
contactsAndGroupsIds: Array<string>; import {
messages: Array<MessageResultProps>; SearchResultsMergedListItem,
searchTerm: string; getHasSearchResults,
}; getSearchResultsList,
getSearchTerm,
} from '../../state/selectors/search';
import { isString } from 'lodash';
const StyledSeparatorSection = styled.div` const StyledSeparatorSection = styled.div`
height: 36px; height: 36px;
@ -35,37 +38,58 @@ const NoResults = styled.div`
text-align: center; text-align: center;
`; `;
export const SearchResults = (props: SearchResultsProps) => { const SectionHeader = ({ title, style }: { title: string; style: CSSProperties }) => {
const { contactsAndGroupsIds, messages, searchTerm } = props; return <StyledSeparatorSection style={style}>{title}</StyledSeparatorSection>;
};
const haveContactsAndGroup = Boolean(contactsAndGroupsIds?.length); function isContact(item: SearchResultsMergedListItem): item is { contactConvoId: string } {
const haveMessages = Boolean(messages?.length); return (item as any).contactConvoId !== undefined;
const noResults = !haveContactsAndGroup && !haveMessages; }
const VirtualizedList = () => {
const searchResultList = useSelector(getSearchResultsList);
return ( return (
<SearchResultsContainer> <AutoSizer>
{noResults ? <NoResults>{window.i18n('noSearchResults', [searchTerm])}</NoResults> : null} {({ height, width }) => (
{haveContactsAndGroup ? ( <List
<> height={height}
<StyledSeparatorSection>{window.i18n('conversationsHeader')}</StyledSeparatorSection> rowCount={searchResultList.length}
{contactsAndGroupsIds.map(conversationId => ( rowHeight={rowPos => {
<ConversationListItem return isString(searchResultList[rowPos.index]) ? 36 : 64;
conversationId={conversationId} }}
key={`search-result-convo-${conversationId}`} rowRenderer={({ index, key, style }) => {
/> const row = searchResultList[index];
))} if (!row) {
</> return null;
) : null} }
if (isString(row)) {
return <SectionHeader title={row} style={style} key={key} />;
}
if (isContact(row)) {
return (
<ConversationListItem conversationId={row.contactConvoId} style={style} key={key} />
);
}
return <MessageSearchResult style={style} key={key} {...row} />;
}}
width={width}
autoHeight={false}
/>
)}
</AutoSizer>
);
};
export const SearchResults = () => {
const searchTerm = useSelector(getSearchTerm);
const hasSearchResults = useSelector(getHasSearchResults);
{haveMessages && ( return (
<> <SearchResultsContainer>
<StyledSeparatorSection> {!hasSearchResults ? (
{`${window.i18n('messagesHeader')}: ${messages.length}`} <NoResults>{window.i18n('noSearchResults', [searchTerm])}</NoResults>
</StyledSeparatorSection> ) : (
{messages.map(message => ( <VirtualizedList />
<MessageSearchResult key={`search-result-message-${message.id}`} {...message} />
))}
</>
)} )}
</SearchResultsContainer> </SearchResultsContainer>
); );

@ -497,27 +497,52 @@ async function getSeenMessagesByHashList(hashes: Array<string>): Promise<any> {
} }
async function removeAllMessagesInConversation(conversationId: string): Promise<void> { async function removeAllMessagesInConversation(conversationId: string): Promise<void> {
const startFunction = Date.now();
let start = Date.now();
let messages; let messages;
do { do {
// Yes, we really want the await in the loop. We're deleting 500 at a // Yes, we really want the await in the loop. We're deleting 500 at a
// time so we don't use too much memory. // time so we don't use too much memory.
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
messages = await getLastMessagesByConversation(conversationId, 500, false); messages = await getLastMessagesByConversation(conversationId, 1000, false);
if (!messages.length) { if (!messages.length) {
return; return;
} }
window.log.info(
const ids = messages.map(message => message.id); `removeAllMessagesInConversation getLastMessagesByConversation ${conversationId} ${
messages.length
} took ${Date.now() - start}ms`
);
// Note: It's very important that these models are fully hydrated because // Note: It's very important that these models are fully hydrated because
// we need to delete all associated on-disk files along with the database delete. // we need to delete all associated on-disk files along with the database delete.
// eslint-disable-next-line no-await-in-loop const ids = messages.map(message => message.id);
start = Date.now();
await Promise.all(messages.map(message => message.cleanup())); for (let index = 0; index < messages.length; index++) {
const message = messages.at(index);
// eslint-disable-next-line no-await-in-loop
await message.cleanup();
}
window.log.info(
`removeAllMessagesInConversation messages.cleanup() ${conversationId} took ${Date.now() -
start}ms`
);
start = Date.now();
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await channels.removeMessagesByIds(ids); await channels.removeMessagesByIds(ids);
} while (messages.length > 0); window.log.info(
`removeAllMessagesInConversation: removeMessagesByIds ${conversationId} took ${Date.now() -
start}ms`
);
} while (messages.length);
await channels.removeAllMessagesInConversation(conversationId);
window.log.info(
`removeAllMessagesInConversation: complete time ${conversationId} took ${Date.now() -
startFunction}ms`
);
} }
async function getMessagesBySentAt(sentAt: number): Promise<MessageCollection> { async function getMessagesBySentAt(sentAt: number): Promise<MessageCollection> {

@ -242,29 +242,29 @@ export function rebuildFtsTable(db: BetterSqlite3.Database) {
db.exec(` db.exec(`
-- Then we create our full-text search table and populate it -- Then we create our full-text search table and populate it
CREATE VIRTUAL TABLE ${MESSAGES_FTS_TABLE} CREATE VIRTUAL TABLE ${MESSAGES_FTS_TABLE}
USING fts5(id UNINDEXED, body); USING fts5(body);
INSERT INTO ${MESSAGES_FTS_TABLE}(id, body) INSERT INTO ${MESSAGES_FTS_TABLE}(rowid, body)
SELECT id, body FROM ${MESSAGES_TABLE}; SELECT rowid, body FROM ${MESSAGES_TABLE};
-- Then we set up triggers to keep the full-text search table up to date -- Then we set up triggers to keep the full-text search table up to date
CREATE TRIGGER messages_on_insert AFTER INSERT ON ${MESSAGES_TABLE} BEGIN CREATE TRIGGER messages_on_insert AFTER INSERT ON ${MESSAGES_TABLE} BEGIN
INSERT INTO ${MESSAGES_FTS_TABLE} ( INSERT INTO ${MESSAGES_FTS_TABLE} (
id, rowid,
body body
) VALUES ( ) VALUES (
new.id, new.rowid,
new.body new.body
); );
END; END;
CREATE TRIGGER messages_on_delete AFTER DELETE ON ${MESSAGES_TABLE} BEGIN CREATE TRIGGER messages_on_delete AFTER DELETE ON ${MESSAGES_TABLE} BEGIN
DELETE FROM ${MESSAGES_FTS_TABLE} WHERE id = old.id; DELETE FROM ${MESSAGES_FTS_TABLE} WHERE rowid = old.rowid;
END; END;
CREATE TRIGGER messages_on_update AFTER UPDATE ON ${MESSAGES_TABLE} WHEN new.body <> old.body BEGIN CREATE TRIGGER messages_on_update AFTER UPDATE ON ${MESSAGES_TABLE} WHEN new.body <> old.body BEGIN
DELETE FROM ${MESSAGES_FTS_TABLE} WHERE id = old.id; DELETE FROM ${MESSAGES_FTS_TABLE} WHERE rowid = old.rowid;
INSERT INTO ${MESSAGES_FTS_TABLE}( INSERT INTO ${MESSAGES_FTS_TABLE}(
id, rowid,
body body
) VALUES ( ) VALUES (
new.id, new.rowid,
new.body new.body
); );
END; END;

@ -33,6 +33,7 @@ import {
} from '../database_utility'; } from '../database_utility';
import { getIdentityKeys, sqlNode } from '../sql'; import { getIdentityKeys, sqlNode } from '../sql';
import { sleepFor } from '../../session/utils/Promise';
const hasDebugEnvVariable = Boolean(process.env.SESSION_DEBUG); const hasDebugEnvVariable = Boolean(process.env.SESSION_DEBUG);
@ -100,6 +101,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToSessionSchemaVersion29, updateToSessionSchemaVersion29,
updateToSessionSchemaVersion30, updateToSessionSchemaVersion30,
updateToSessionSchemaVersion31, updateToSessionSchemaVersion31,
updateToSessionSchemaVersion32,
]; ];
function updateToSessionSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) { function updateToSessionSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) {
@ -1204,7 +1206,6 @@ function updateToSessionSchemaVersion29(currentVersion: number, db: BetterSqlite
conversationId conversationId
);`); );`);
rebuildFtsTable(db); rebuildFtsTable(db);
// Keeping this empty migration because some people updated to this already, even if it is not needed anymore
writeSessionSchemaVersion(targetVersion, db); writeSessionSchemaVersion(targetVersion, db);
})(); })();
@ -1235,6 +1236,8 @@ function insertContactIntoContactWrapper(
dbProfileKey: contact.profileKey || undefined, dbProfileKey: contact.profileKey || undefined,
dbProfileUrl: contact.avatarPointer || undefined, dbProfileUrl: contact.avatarPointer || undefined,
priority, priority,
dbCreatedAtSeconds: Math.floor((contact.active_at || Date.now()) / 1000),
// expirationTimerSeconds, // expirationTimerSeconds,
}); });
@ -1259,6 +1262,7 @@ function insertContactIntoContactWrapper(
dbProfileKey: undefined, dbProfileKey: undefined,
dbProfileUrl: undefined, dbProfileUrl: undefined,
priority: CONVERSATION_PRIORITIES.default, priority: CONVERSATION_PRIORITIES.default,
dbCreatedAtSeconds: Math.floor(Date.now() / 1000),
// expirationTimerSeconds: 0, // expirationTimerSeconds: 0,
}) })
); );
@ -1308,7 +1312,7 @@ function insertCommunityIntoWrapper(
const convoId = community.id; // the id of a conversation has the prefix, the serverUrl and the roomToken already present, but not the pubkey const convoId = community.id; // the id of a conversation has the prefix, the serverUrl and the roomToken already present, but not the pubkey
const roomDetails = sqlNode.getV2OpenGroupRoom(convoId, db); const roomDetails = sqlNode.getV2OpenGroupRoom(convoId, db);
hasDebugEnvVariable && console.info('insertCommunityIntoWrapper: ', community); // hasDebugEnvVariable && console.info('insertCommunityIntoWrapper: ', community);
if ( if (
!roomDetails || !roomDetails ||
@ -1832,6 +1836,26 @@ function updateToSessionSchemaVersion31(currentVersion: number, db: BetterSqlite
})(); })();
} }
function updateToSessionSchemaVersion32(currentVersion: number, db: BetterSqlite3.Database) {
const targetVersion = 32;
if (currentVersion >= targetVersion) {
return;
}
console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`);
db.transaction(() => {
db.exec(`CREATE INDEX messages_conversationId ON ${MESSAGES_TABLE} (
conversationId
);`);
dropFtsAndTriggers(db);
rebuildFtsTable(db);
writeSessionSchemaVersion(targetVersion, db);
})();
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);
}
export function printTableColumns(table: string, db: BetterSqlite3.Database) { export function printTableColumns(table: string, db: BetterSqlite3.Database) {
console.info(db.pragma(`table_info('${table}');`)); console.info(db.pragma(`table_info('${table}');`));
} }
@ -1846,7 +1870,7 @@ function writeSessionSchemaVersion(newVersion: number, db: BetterSqlite3.Databas
).run({ newVersion }); ).run({ newVersion });
} }
export function updateSessionSchema(db: BetterSqlite3.Database) { export async function updateSessionSchema(db: BetterSqlite3.Database) {
const result = db const result = db
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema';`) .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema';`)
.get(); .get();
@ -1863,5 +1887,8 @@ export function updateSessionSchema(db: BetterSqlite3.Database) {
for (let index = 0, max = LOKI_SCHEMA_VERSIONS.length; index < max; index += 1) { for (let index = 0, max = LOKI_SCHEMA_VERSIONS.length; index < max; index += 1) {
const runSchemaUpdate = LOKI_SCHEMA_VERSIONS[index]; const runSchemaUpdate = LOKI_SCHEMA_VERSIONS[index];
runSchemaUpdate(lokiSchemaVersion, db); runSchemaUpdate(lokiSchemaVersion, db);
if (index > lokiSchemaVersion) {
await sleepFor(200); // give some time for the UI to not freeze between 2 migrations
}
} }
} }

@ -527,7 +527,7 @@ const SCHEMA_VERSIONS = [
updateToSchemaVersion11, updateToSchemaVersion11,
]; ];
export function updateSchema(db: BetterSqlite3.Database) { export async function updateSchema(db: BetterSqlite3.Database) {
const sqliteVersion = getSQLiteVersion(db); const sqliteVersion = getSQLiteVersion(db);
const sqlcipherVersion = getSQLCipherVersion(db); const sqlcipherVersion = getSQLCipherVersion(db);
const userVersion = getUserVersion(db); const userVersion = getUserVersion(db);
@ -545,7 +545,7 @@ export function updateSchema(db: BetterSqlite3.Database) {
const runSchemaUpdate = SCHEMA_VERSIONS[index]; const runSchemaUpdate = SCHEMA_VERSIONS[index];
runSchemaUpdate(schemaVersion, db); runSchemaUpdate(schemaVersion, db);
} }
updateSessionSchema(db); await updateSessionSchema(db);
} }
function migrateSchemaVersion(db: BetterSqlite3.Database) { function migrateSchemaVersion(db: BetterSqlite3.Database) {

@ -28,7 +28,6 @@ import {
ATTACHMENT_DOWNLOADS_TABLE, ATTACHMENT_DOWNLOADS_TABLE,
CLOSED_GROUP_V2_KEY_PAIRS_TABLE, CLOSED_GROUP_V2_KEY_PAIRS_TABLE,
CONVERSATIONS_TABLE, CONVERSATIONS_TABLE,
dropFtsAndTriggers,
formatRowOfConversation, formatRowOfConversation,
GUARD_NODE_TABLE, GUARD_NODE_TABLE,
HEX_KEY, HEX_KEY,
@ -41,7 +40,6 @@ import {
NODES_FOR_PUBKEY_TABLE, NODES_FOR_PUBKEY_TABLE,
objectToJSON, objectToJSON,
OPEN_GROUP_ROOMS_V2_TABLE, OPEN_GROUP_ROOMS_V2_TABLE,
rebuildFtsTable,
toSqliteBoolean, toSqliteBoolean,
} from './database_utility'; } from './database_utility';
import { LocaleMessagesType } from './locale'; // checked - only node import { LocaleMessagesType } from './locale'; // checked - only node
@ -166,7 +164,7 @@ async function initializeSql({
if (!db) { if (!db) {
throw new Error('db is not set'); throw new Error('db is not set');
} }
updateSchema(db); await updateSchema(db);
// test database // test database
@ -698,9 +696,9 @@ function searchMessages(query: string, limit: number) {
${MESSAGES_TABLE}.json, ${MESSAGES_TABLE}.json,
snippet(${MESSAGES_FTS_TABLE}, -1, '<<left>>', '<<right>>', '...', 5) as snippet snippet(${MESSAGES_FTS_TABLE}, -1, '<<left>>', '<<right>>', '...', 5) as snippet
FROM ${MESSAGES_FTS_TABLE} FROM ${MESSAGES_FTS_TABLE}
INNER JOIN ${MESSAGES_TABLE} on ${MESSAGES_FTS_TABLE}.id = ${MESSAGES_TABLE}.id INNER JOIN ${MESSAGES_TABLE} on ${MESSAGES_FTS_TABLE}.rowid = ${MESSAGES_TABLE}.rowid
WHERE WHERE
${MESSAGES_FTS_TABLE} match $query ${MESSAGES_FTS_TABLE}.body match $query
${orderByMessageCoalesceClause} ${orderByMessageCoalesceClause}
LIMIT $limit;` LIMIT $limit;`
) )
@ -958,11 +956,12 @@ function removeMessagesByIds(ids: Array<string>, instance?: BetterSqlite3.Databa
if (!ids.length) { if (!ids.length) {
throw new Error('removeMessagesByIds: No ids to delete!'); throw new Error('removeMessagesByIds: No ids to delete!');
} }
const start = Date.now();
// Our node interface doesn't seem to allow you to replace one single ? with an array
assertGlobalInstanceOrInstance(instance) assertGlobalInstanceOrInstance(instance)
.prepare(`DELETE FROM ${MESSAGES_TABLE} WHERE id IN ( ${ids.map(() => '?').join(', ')} );`) .prepare(`DELETE FROM ${MESSAGES_TABLE} WHERE id IN ( ${ids.map(() => '?').join(', ')} );`)
.run(ids); .run(ids);
console.log(`removeMessagesByIds of length ${ids.length} took ${Date.now() - start}ms`);
} }
function removeAllMessagesInConversation( function removeAllMessagesInConversation(
@ -972,9 +971,9 @@ function removeAllMessagesInConversation(
if (!conversationId) { if (!conversationId) {
return; return;
} }
const inst = assertGlobalInstanceOrInstance(instance);
// Our node interface doesn't seem to allow you to replace one single ? with an array inst
assertGlobalInstanceOrInstance(instance)
.prepare(`DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = $conversationId`) .prepare(`DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = $conversationId`)
.run({ conversationId }); .run({ conversationId });
} }
@ -2233,7 +2232,6 @@ function cleanUpOldOpengroupsOnStart() {
// first remove very old messages for each opengroups // first remove very old messages for each opengroups
const db = assertGlobalInstance(); const db = assertGlobalInstance();
db.transaction(() => { db.transaction(() => {
dropFtsAndTriggers(db);
v2ConvosIds.forEach(convoId => { v2ConvosIds.forEach(convoId => {
const messagesInConvoBefore = getMessagesCountByConversation(convoId); const messagesInConvoBefore = getMessagesCountByConversation(convoId);
@ -2316,8 +2314,6 @@ function cleanUpOldOpengroupsOnStart() {
} }
cleanUpMessagesJson(); cleanUpMessagesJson();
rebuildFtsTable(db);
})(); })();
} }

@ -294,7 +294,7 @@ async function queueNewJobIfNeeded() {
!lastRunConfigSyncJobTimestamp || !lastRunConfigSyncJobTimestamp ||
lastRunConfigSyncJobTimestamp < Date.now() - defaultMsBetweenRetries lastRunConfigSyncJobTimestamp < Date.now() - defaultMsBetweenRetries
) { ) {
window.log.debug('Scheduling ConfSyncJob: ASAP'); // window.log.debug('Scheduling ConfSyncJob: ASAP');
// we postpone by 1000ms to make sure whoever is adding this job is done with what is needs to do first // we postpone by 1000ms to make sure whoever is adding this job is done with what is needs to do first
// this call will make sure that there is only one configuration sync job at all times // this call will make sure that there is only one configuration sync job at all times
await runners.configurationSyncRunner.addJob( await runners.configurationSyncRunner.addJob(
@ -305,7 +305,7 @@ async function queueNewJobIfNeeded() {
const diff = Math.max(Date.now() - lastRunConfigSyncJobTimestamp, 0); const diff = Math.max(Date.now() - lastRunConfigSyncJobTimestamp, 0);
// but we want to run every 30, so what we need is actually `30-10` from now = 20 // but we want to run every 30, so what we need is actually `30-10` from now = 20
const leftBeforeNextTick = Math.max(defaultMsBetweenRetries - diff, 1000); const leftBeforeNextTick = Math.max(defaultMsBetweenRetries - diff, 1000);
window.log.debug('Scheduling ConfSyncJob: LATER'); // window.log.debug('Scheduling ConfSyncJob: LATER');
await runners.configurationSyncRunner.addJob( await runners.configurationSyncRunner.addJob(
new ConfigurationSyncJob({ nextAttemptTimestamp: Date.now() + leftBeforeNextTick }) new ConfigurationSyncJob({ nextAttemptTimestamp: Date.now() + leftBeforeNextTick })

@ -70,6 +70,7 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise<voi
dbProfileKey, dbProfileKey,
dbProfileUrl, dbProfileUrl,
priority, priority,
dbCreatedAtSeconds: 0, // just give 0, now() will be used internally by the wrapper if the contact does not exist yet.
// expirationTimerSeconds, // expirationTimerSeconds,
}); });
try { try {

@ -31,7 +31,7 @@ import { getIntl } from './user';
import { filter, isEmpty, isNumber, pick, sortBy } from 'lodash'; import { filter, isEmpty, isNumber, pick, sortBy } from 'lodash';
import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions'; import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions';
import { getSelectedConversation, getSelectedConversationKey } from './selectedConversation'; import { getSelectedConversationKey } from './selectedConversation';
import { getModeratorsOutsideRedux } from './sogsRoomInfo'; import { getModeratorsOutsideRedux } from './sogsRoomInfo';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations; export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -626,6 +626,17 @@ export const isFirstUnreadMessageIdAbove = createSelector(
const getMessageId = (_whatever: any, id: string | undefined) => id; const getMessageId = (_whatever: any, id: string | undefined) => id;
/**
* A lot of our UI changes on the main panel need to happen quickly (composition box).
*/
export const getSelectedConversation = createSelector(
getConversationLookup,
getSelectedConversationKey,
(lookup, selectedConvo) => {
return selectedConvo ? lookup[selectedConvo] : undefined;
}
);
// tslint:disable: cyclomatic-complexity // tslint:disable: cyclomatic-complexity
export const getMessagePropsByMessageId = createSelector( export const getMessagePropsByMessageId = createSelector(

@ -1,11 +1,12 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { compact } from 'lodash'; import { compact, isEmpty } from 'lodash';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { ConversationLookupType } from '../ducks/conversations'; import { ConversationLookupType } from '../ducks/conversations';
import { SearchStateType } from '../ducks/search'; import { SearchStateType } from '../ducks/search';
import { getConversationLookup } from './conversations'; import { getConversationLookup } from './conversations';
import { MessageResultProps } from '../../components/search/MessageSearchResults';
export const getSearch = (state: StateType): SearchStateType => state.search; export const getSearch = (state: StateType): SearchStateType => state.search;
@ -41,6 +42,10 @@ const getSearchResults = createSelector(
} }
); );
export const getSearchTerm = createSelector([getSearchResults], searchResult => {
return searchResult.searchTerm;
});
export const getSearchResultsIdsOnly = createSelector([getSearchResults], searchState => { export const getSearchResultsIdsOnly = createSelector([getSearchResults], searchState => {
return { return {
...searchState, ...searchState,
@ -48,6 +53,32 @@ export const getSearchResultsIdsOnly = createSelector([getSearchResults], search
}; };
}); });
export const getHasSearchResults = createSelector([getSearchResults], searchState => {
return !isEmpty(searchState.contactsAndGroups) || !isEmpty(searchState.messages);
});
export const getSearchResultsContactOnly = createSelector([getSearchResults], searchState => { export const getSearchResultsContactOnly = createSelector([getSearchResults], searchState => {
return searchState.contactsAndGroups.filter(m => m.isPrivate).map(m => m.id); return searchState.contactsAndGroups.filter(m => m.isPrivate).map(m => m.id);
}); });
/**
*
* When type is string, we render a sectionHeader.
* When type just has a conversationId field, we render a ConversationListItem.
* When type is MessageResultProps we render a MessageSearchResult
*/
export type SearchResultsMergedListItem = string | { contactConvoId: string } | MessageResultProps;
export const getSearchResultsList = createSelector([getSearchResults], searchState => {
const { contactsAndGroups, messages } = searchState;
const builtList: Array<SearchResultsMergedListItem> = [];
if (contactsAndGroups.length) {
builtList.push(window.i18n('conversationsHeader', [`${contactsAndGroups.length}`]));
builtList.push(...contactsAndGroups.map(m => ({ contactConvoId: m.id })));
}
if (messages.length) {
builtList.push(window.i18n('searchMessagesHeader', [`${messages.length}`]));
builtList.push(...messages);
}
return builtList;
});

@ -3,9 +3,9 @@ import { useSelector } from 'react-redux';
import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes'; import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes';
import { PubKey } from '../../session/types'; import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils'; import { UserUtils } from '../../session/utils';
import { ReduxConversationType } from '../ducks/conversations';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo'; import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo';
import { getSelectedConversation } from './conversations';
/** /**
* Returns the formatted text for notification setting. * Returns the formatted text for notification setting.
@ -58,11 +58,6 @@ export const getSelectedConversationKey = (state: StateType): string | undefined
return state.conversations.selectedConversation; return state.conversations.selectedConversation;
}; };
export const getSelectedConversation = (state: StateType): ReduxConversationType | undefined => {
const selected = getSelectedConversationKey(state);
return selected ? state.conversations.conversationLookup[selected] : undefined;
};
/** /**
* Returns true if the current conversation selected is a public group and false otherwise. * Returns true if the current conversation selected is a public group and false otherwise.
*/ */

@ -6,15 +6,13 @@ import { getHasOngoingCallWithFocusedConvo } from '../selectors/call';
import { import {
getIsSelectedConvoInitialLoadingInProgress, getIsSelectedConvoInitialLoadingInProgress,
getLightBoxOptions, getLightBoxOptions,
getSelectedConversation,
getSelectedMessageIds, getSelectedMessageIds,
getSortedMessagesOfSelectedConversation, getSortedMessagesOfSelectedConversation,
isMessageDetailView, isMessageDetailView,
isRightPanelShowing, isRightPanelShowing,
} from '../selectors/conversations'; } from '../selectors/conversations';
import { import { getSelectedConversationKey } from '../selectors/selectedConversation';
getSelectedConversation,
getSelectedConversationKey,
} from '../selectors/selectedConversation';
import { getStagedAttachmentsForCurrentConversation } from '../selectors/stagedAttachments'; import { getStagedAttachmentsForCurrentConversation } from '../selectors/stagedAttachments';
import { getTheme } from '../selectors/theme'; import { getTheme } from '../selectors/theme';
import { getOurNumber } from '../selectors/user'; import { getOurNumber } from '../selectors/user';

@ -73,6 +73,7 @@ export type LocalizerKeys =
| 'conversationsHeader' | 'conversationsHeader'
| 'contactsHeader' | 'contactsHeader'
| 'messagesHeader' | 'messagesHeader'
| 'searchMessagesHeader'
| 'settingsHeader' | 'settingsHeader'
| 'typingAlt' | 'typingAlt'
| 'contactAvatarAlt' | 'contactAvatarAlt'

@ -115,6 +115,7 @@ export function getContactInfoFromDBValues({
priority, priority,
dbProfileUrl, dbProfileUrl,
dbProfileKey, dbProfileKey,
dbCreatedAtSeconds,
}: // expirationTimerSeconds, }: // expirationTimerSeconds,
{ {
id: string; id: string;
@ -124,6 +125,7 @@ export function getContactInfoFromDBValues({
dbNickname: string | undefined; dbNickname: string | undefined;
dbName: string | undefined; dbName: string | undefined;
priority: number; priority: number;
dbCreatedAtSeconds: number;
dbProfileUrl: string | undefined; dbProfileUrl: string | undefined;
dbProfileKey: string | undefined; dbProfileKey: string | undefined;
// expirationTimerSeconds: number | undefined; // expirationTimerSeconds: number | undefined;
@ -136,6 +138,7 @@ export function getContactInfoFromDBValues({
priority, priority,
nickname: dbNickname, nickname: dbNickname,
name: dbName, name: dbName,
createdAtSeconds: dbCreatedAtSeconds,
// expirationTimerSeconds: // expirationTimerSeconds:
// !!expirationTimerSeconds && isFinite(expirationTimerSeconds) && expirationTimerSeconds > 0 // !!expirationTimerSeconds && isFinite(expirationTimerSeconds) && expirationTimerSeconds > 0
// ? expirationTimerSeconds // ? expirationTimerSeconds

@ -411,13 +411,20 @@
dependencies: dependencies:
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.19.0" version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.3.4":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.18.6": "@babel/template@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31"
@ -1192,10 +1199,10 @@
dependencies: dependencies:
"@types/react" "^17" "@types/react" "^17"
"@types/react-mentions@^4.1.1": "@types/react-mentions@^4.1.8":
version "4.1.6" version "4.1.8"
resolved "https://registry.yarnpkg.com/@types/react-mentions/-/react-mentions-4.1.6.tgz#0ecdb61785c22edbf9c7d6718505d4814ad3a65c" resolved "https://registry.yarnpkg.com/@types/react-mentions/-/react-mentions-4.1.8.tgz#4bebe54c5c74181d8eedf1e613a208d03b4a8d7e"
integrity sha512-f4/BdnjlMxT47q+WqlcYYwFABbBMVQrDoFFeMeljtFC5nnR9/x8TOFmN18BJKgNuWMgivy9uE5EKtsjlay751w== integrity sha512-Go86ozdnh0FTNbiGiDPAcNqYqtab9iGzLOgZPYUKrnhI4539jGzfJtP6rFHcXgi9Koe58yhkeyKYib6Ucul/sQ==
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
@ -5148,9 +5155,9 @@ levn@~0.3.0:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
type-check "~0.3.2" type-check "~0.3.2"
"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.19/libsession_util_nodejs-v0.1.19.tar.gz": "libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.20/libsession_util_nodejs-v0.1.20.tar.gz":
version "0.1.19" version "0.1.20"
resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.19/libsession_util_nodejs-v0.1.19.tar.gz#294c6e8ea6b767d375a9c0249bef98b65f3ae252" resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.20/libsession_util_nodejs-v0.1.20.tar.gz#4ff8331e4efb1725cf8bba0ef1af7496ebd8533d"
dependencies: dependencies:
cmake-js "^7.2.1" cmake-js "^7.2.1"
node-addon-api "^6.1.0" node-addon-api "^6.1.0"
@ -6551,10 +6558,10 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-mentions@^4.2.0: react-mentions@^4.4.9:
version "4.3.2" version "4.4.9"
resolved "https://registry.yarnpkg.com/react-mentions/-/react-mentions-4.3.2.tgz#93a2a674648f31d2b40e8c90d30b94c377cbb582" resolved "https://registry.yarnpkg.com/react-mentions/-/react-mentions-4.4.9.tgz#5f68c7978c107518646c5c34c47515c30259e23a"
integrity sha512-NV8ixuE5W9zuvBNWLpPlO+f4QYEkR+p6mR3Jfpfcbytrqqn2nbVb27YXE/M4qSP8N8C+ktgeMUV4jVhm86gt1A== integrity sha512-CUDt8GOVbAmo3o+a8l1UxcJ/gJMdFdqeiJM3U5+krcNoUwyKv7Zcy67WfFZQJfChpJ8LTiD0FtCSRoyEzC6Ysw==
dependencies: dependencies:
"@babel/runtime" "7.4.5" "@babel/runtime" "7.4.5"
invariant "^2.2.4" invariant "^2.2.4"
@ -6759,7 +6766,12 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.13.4:
version "0.13.9" version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==

Loading…
Cancel
Save